selenium_auth.py•14.8 kB
"""
Selenium script to automate getting oauth_token from Google accounts
and exchange it for a master token
"""
import time
import sys
import platform
import getpass as getpass_module
try:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
except ImportError:
print("❌ Selenium is required for automated authentication.")
print("Install it with: pip install selenium")
sys.exit(1)
try:
from gpsoauth import exchange_token
except ImportError:
print("❌ gpsoauth is required for token exchange.")
print("This should have been installed with wlater-mcp.")
print("Try: pip install gpsoauth")
sys.exit(1)
from wlater_mcp.credentials import generate_android_id
# Windows-specific imports
if sys.platform == 'win32':
try:
import msvcrt
except ImportError:
msvcrt = None
def check_for_exit():
"""Check if user pressed ESC to exit"""
if sys.platform == 'win32' and msvcrt:
if msvcrt.kbhit():
key = msvcrt.getch()
if key == b'\x1b': # ESC key
print("\n\n🛑 Process cancelled by user (ESC pressed)")
print("Exiting...")
sys.exit(0)
# For non-Windows systems, we'll handle Ctrl+C naturally
def get_oauth_token_selenium(email, password):
"""
Opens browser, auto-fills email/password, and retrieves oauth_token from cookies
"""
# Initialize Chrome driver with options to avoid automation detection
print("🚀 Starting Chrome browser...")
options = webdriver.ChromeOptions()
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
driver = webdriver.Chrome(options=options)
try:
# Navigate to Google Embedded Setup page (v2/android version more reliable)
url = "https://accounts.google.com/embedded/setup/v2/android"
print(f"📍 Navigating to {url}")
driver.get(url)
# Wait for email field to load
print("⏳ Waiting for email field...")
time.sleep(3)
try:
# Find and fill email field
email_field = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='email']"))
)
print(f"📧 Filling in email: {email}")
email_field.send_keys(email)
time.sleep(1)
# Click Next button
next_button = driver.find_element(By.ID, "identifierNext")
next_button.click()
print("✓ Clicked Next (email)")
# Wait for password field or captcha
print("⏳ Waiting for password field...")
print("⚠️ If you see a CAPTCHA, please solve it in the browser...")
# Extended wait time for captcha solving
password_field = WebDriverWait(driver, 60).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='password']"))
)
print("✓ Password field found")
time.sleep(2)
print("🔑 Filling in password...")
password_field.send_keys(password)
time.sleep(1)
print("✓ Password filled (you may need to click 'Next' manually)")
except Exception as e:
print(f"⚠️ Could not auto-fill credentials: {e}")
print("Please fill them in manually in the browser window...")
print("\n" + "="*60)
print("📝 MANUAL STEPS REQUIRED:")
print("="*60)
print("1. If prompted, complete any 2FA verification")
print(" (e.g., press a number on your phone to confirm it's you)")
print("2. Click 'I agree' or 'Agree' when you see the terms")
print("="*60)
print("\n" + "="*60)
print("📝 MANUAL STEPS IN BROWSER:")
print("="*60)
print("1. Solve any CAPTCHA if it appears")
print("2. Click 'Next' on the password page (if not already clicked)")
print("3. If prompted, complete any 2FA verification")
print(" (e.g., press a number on your phone to confirm it's you)")
print("4. Click 'I agree' or 'Agree' when you see the terms")
print("5. ⚠️ IMPORTANT: Wait 5-10 seconds after 'I agree'")
print("="*60)
# Prompt user to complete login
print("\n⚠️ DO NOT PRESS ENTER TOO QUICKLY!")
print("📌 Only press ENTER after:")
print(" ✓ You clicked 'I agree'")
print(" ✓ You waited 5-10 seconds for page to load")
print(" ✓ Page has Loaded for 5-10 seconds after 'I agree'")
input("\n👉 Ready? Press ENTER to extract token...")
# Give more time for cookies to be set and page to stabilize
print("⏳ Waiting for cookies to be set...")
time.sleep(5)
# Check current URL to debug
current_url = driver.current_url
print(f"📍 Current URL: {current_url}")
# Get all cookies
all_cookies = driver.get_cookies()
# Look for oauth_token in cookies from accounts.google.com
oauth_token = None
for cookie in all_cookies:
if cookie['name'] == 'oauth_token' and 'google.com' in cookie['domain']:
oauth_token = cookie['value']
break
if oauth_token:
print("\n✅ SUCCESS! Found oauth_token:")
print("-" * 60)
print(oauth_token)
print("-" * 60)
# Check if it starts with expected prefix
if oauth_token.startswith('oauth2_4/'):
print("✓ Token format looks correct (starts with 'oauth2_4/')")
else:
print("⚠️ Warning: Token doesn't start with 'oauth2_4/' - this might be incorrect")
return oauth_token
else:
print("\n❌ ERROR: Could not find oauth_token in cookies!")
print("\n🔍 Found these cookies from google.com:")
for cookie in all_cookies:
if 'google.com' in cookie['domain']:
print(f" - {cookie['name']}")
print("\n💡 Tips:")
print(" - Make sure you clicked 'I agree' on the setup page")
print(" - The page should show 'Setup successful' or similar")
print(" - Try waiting a bit longer before pressing ENTER")
return None
except Exception as e:
print(f"\n❌ Error occurred: {e}")
import traceback
traceback.print_exc()
return None
finally:
# Ask before closing
input("\n🔍 Browser will close when you press ENTER...")
driver.quit()
print("✋ Browser closed")
def get_master_token(email, oauth_token, android_id="deadbeefdeadbeef"):
"""
Exchange oauth_token for master token using gpsoauth
"""
print("\n" + "="*60)
print("🔄 Exchanging OAuth token for Master token...")
print("="*60)
try:
response = exchange_token(email, oauth_token, android_id)
# Response is a dict with format: {'Token': 'aas_et/...', 'Auth': '...', ...}
if isinstance(response, dict) and 'Token' in response:
master_token = response['Token']
print("\n✅ SUCCESS! Your master token is:")
print("-" * 60)
print(master_token)
print("-" * 60)
# Check if it starts with expected prefix
if master_token.startswith('aas_et/'):
print("✓ Token format looks correct (starts with 'aas_et/')")
else:
print("⚠️ Warning: Token doesn't start with 'aas_et/' - this might be incorrect")
print("\n💾 Save this token securely. You'll use it to authenticate with gkeepapi.")
return master_token
else:
# Fallback if response is already a string
print("\n✅ SUCCESS! Your master token is:")
print("-" * 60)
print(response)
print("-" * 60)
print("\n💾 Save this token securely. You'll use it to authenticate with gkeepapi.")
return response
except Exception as e:
print(f"\n❌ Error exchanging token: {e}", file=sys.stderr)
print("Make sure the OAuth token is valid and not expired.", file=sys.stderr)
print("\nℹ️ OAuth tokens expire quickly (minutes). Try running the script again.", file=sys.stderr)
return None
def run_selenium_auth():
"""Run selenium authentication and return credentials.
This function is designed to be called by setup.py for programmatic use.
Prompts for email and password, generates android_id, extracts oauth_token,
and exchanges it for master_token.
Returns:
Tuple of (email, master_token, android_id) on success, None on failure
"""
try:
# Generate Android ID using credentials module
print("\n🔧 Generating Android ID...")
android_id = generate_android_id()
print(f"✓ Generated Android ID: {android_id}")
# Get email
email = input("\nEnter your Google email: ").strip()
if not email:
print("❌ Email is required!")
return None
# Get password
password = getpass_module.getpass("Enter your Google password: ").strip()
if not password:
print("❌ Password is required!")
return None
# Get OAuth token via selenium
oauth_token = get_oauth_token_selenium(email, password)
if not oauth_token:
return None
# Exchange for master token
master_token = get_master_token(email, oauth_token, android_id)
if not master_token:
return None
return (email, master_token, android_id)
except KeyboardInterrupt:
print("\n\n🛑 Process cancelled by user")
return None
except Exception as e:
print(f"\n❌ Error: {e}")
return None
def main():
try:
print("="*60)
print(" Google Master Token Generator (Selenium + gpsoauth)")
print("="*60)
print("\nThis script will:")
print("1. Open Chrome browser")
print("2. Navigate to Google's embedded setup page")
print("3. Auto-fill your email and password")
print("4. Wait for you to complete 2FA and click 'I agree'")
print("5. Extract the oauth_token from cookies")
print("6. Exchange it for a master token")
print("\n⚠️ Make sure you have Chrome and ChromeDriver installed!")
if sys.platform == 'win32':
print("💡 Press ESC at any time to cancel")
else:
print("💡 Press Ctrl+C at any time to cancel")
print("="*60)
# Generate Android ID
print("\n" + "="*60)
print("🔧 Generating Android ID...")
print("="*60)
try:
generated_id = generate_android_id()
print(f"\n📱 Generated Android ID: {generated_id}")
except Exception as e:
print(f"⚠️ Error generating Android ID: {e}")
generated_id = "deadbeefdeadbeef"
print(f"Using default: {generated_id}")
print("\n" + "="*60)
# Get email first
check_for_exit()
email = input("\nEnter your Google email: ").strip()
check_for_exit()
if not email:
print("❌ Email is required!")
sys.exit(1)
# Prompt for Android ID with validation
print("\n💡 Press ENTER to use the generated ID, or type your own custom 16-character hex ID")
while True:
check_for_exit()
android_id_input = input(f"\nAndroid ID [{generated_id}]: ").strip()
check_for_exit()
android_id = android_id_input if android_id_input else generated_id
# Validate Android ID format
if len(android_id) != 16:
print(f"❌ Invalid Android ID! Must be exactly 16 characters (got {len(android_id)})")
print(" Press Enter to use generated ID, or enter a valid 16-character hex ID")
continue
# Check if all characters are valid hexadecimal
try:
int(android_id, 16) # This will raise ValueError if not valid hex
break # Valid Android ID
except ValueError:
print("❌ Invalid Android ID! Must contain only hexadecimal characters (0-9, a-f)")
print(" Press Enter to use generated ID, or enter a valid hex ID")
continue
print(f"\n✓ Using Android ID: {android_id}")
check_for_exit()
input("\n⚠️ Browser will open. You'll need to click 'Next' after password is filled.\nPress ENTER to start...")
check_for_exit()
# Get password just before opening browser (more secure)
password = getpass_module.getpass("\nEnter your Google password: ").strip()
check_for_exit()
if not password:
print("❌ Password is required!")
sys.exit(1)
oauth_token = get_oauth_token_selenium(email, password)
if oauth_token:
# Exchange for master token
master_token = get_master_token(email, oauth_token, android_id)
if master_token:
print("\n" + "="*60)
print("✅ COMPLETE! You now have your master token.")
print("="*60)
else:
print("\n" + "="*60)
print("❌ Failed to get master token")
print("="*60)
sys.exit(1)
else:
print("\n" + "="*60)
print("❌ Failed to retrieve oauth_token")
print("Try again or use manual method from AUTH_INSTRUCTIONS.md")
print("="*60)
sys.exit(1)
except KeyboardInterrupt:
print("\n\n🛑 Process cancelled by user (Ctrl+C)")
print("Exiting...")
sys.exit(0)
if __name__ == "__main__":
main()