"""
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()