diff --git a/flake.nix b/flake.nix index 2c7eb60..3424c34 100644 --- a/flake.nix +++ b/flake.nix @@ -12,6 +12,9 @@ pkgs.python3Packages.selenium pkgs.chromedriver pkgs.chromium + pkgs.rbw + pkgs.wl-clipboard + pkgs.xclip ]; in { devShells.${system}.default = pkgs.mkShell { diff --git a/selenium_cookie_extractor_json.py b/selenium_cookie_extractor_json.py index 96e0386..5b1e729 100644 --- a/selenium_cookie_extractor_json.py +++ b/selenium_cookie_extractor_json.py @@ -1,69 +1,204 @@ #!/usr/bin/env python3 -import time +import argparse import json +import subprocess +import sys +import time + from selenium import webdriver from selenium.webdriver.chrome.service import Service +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait -# Configure Chrome options to run in incognito mode. -options = webdriver.ChromeOptions() -options.add_argument("--incognito") -# Uncomment the following option if you want to auto-open developer tools for each tab. -# options.add_argument("--auto-open-devtools-for-tabs") -# Initialize the Chrome driver. -# If ChromeDriver is not in your PATH, specify its location: Service('/path/to/chromedriver') -service = Service() # Assumes chromedriver is in your PATH +def check_rbw_unlocked(): + """Check if rbw vault is unlocked.""" + result = subprocess.run(["rbw", "unlocked"], capture_output=True) + return result.returncode == 0 -driver = webdriver.Chrome(service=service, options=options) -print("Opening https://chat.google.com ...") -driver.get("https://chat.google.com") +def get_rbw_credentials(entry: str): + """Get username and password from rbw.""" + # Get password + result = subprocess.run( + ["rbw", "get", entry], + capture_output=True, + text=True, + ) + if result.returncode != 0: + return None, None -print("\nA new incognito window has been opened.") -print("Please log in normally. To inspect cookies manually:") -print(" 1. Press F12 to open developer tools.") -print(" 2. Navigate to the Application (Chrome) or Storage (Firefox) tab.") -print(" 3. Expand Cookies and select https://chat.google.com.") -print(" 4. Verify that the COMPASS, SSID, SID, OSID, and HSID cookies are present.") -print("\nIMPORTANT: Once you are logged in and have confirmed the cookies (or just logged in),") -print("press Enter here. (Remember: to keep the validity of these cookies, close the browser soon!)") -input("Press Enter to extract cookies...") + password = result.stdout.strip() -# Navigate to some invalid URL on chat.google.com. This way we won't be redirected back to a -# mail.google.com page and this allows us to extract the correct cookies. -driver.get("https://chat.google.com/u/0/mole/world") + # Get username + result = subprocess.run( + ["rbw", "get", "--field", "username", entry], + capture_output=True, + text=True, + ) + username = result.stdout.strip() if result.returncode == 0 else None -# Get all cookies -all_cookies = driver.get_cookies() + return username, password -# Define the cookie names we want (case-insensitive) -target_names = {"COMPASS", "SSID", "SID", "OSID", "HSID"} -extracted = {} -# For COMPASS cookie, if multiple are present, prefer the one with path == "/" -compass_cookie = None +def copy_to_clipboard(text: str): + """Copy text to clipboard using wl-copy or xclip.""" + # Try wl-copy first (Wayland) + try: + subprocess.run( + ["wl-copy"], + input=text.encode(), + check=True, + capture_output=True, + ) + return True + except (subprocess.CalledProcessError, FileNotFoundError): + pass -for cookie in all_cookies: - name_upper = cookie["name"].upper() - if name_upper not in target_names: - continue - if name_upper == "COMPASS": - if cookie.get("path", "") == "/": - compass_cookie = cookie - elif compass_cookie is None: - compass_cookie = cookie - else: - extracted[name_upper] = cookie["value"] + # Fall back to xclip (X11) + try: + subprocess.run( + ["xclip", "-selection", "clipboard"], + input=text.encode(), + check=True, + capture_output=True, + ) + return True + except (subprocess.CalledProcessError, FileNotFoundError): + pass -if compass_cookie: - extracted["COMPASS"] = compass_cookie["value"] + return False -# Form JSON object of the extracted cookies. -json_data = json.dumps(extracted, indent=2) -print("\nExtracted Cookie JSON:") -print(json_data) -print("\nClosing the browser window to avoid invalidating the cookies (Google uses refresh tokens).") +def automate_google_login(driver, username: str, password: str): + """Attempt to automate Google login. Returns True if successful.""" + wait = WebDriverWait(driver, 10) -# Close the browser window. -driver.quit() + try: + # Wait for and fill email field + email_field = wait.until( + EC.presence_of_element_located((By.CSS_SELECTOR, 'input[type="email"]')) + ) + email_field.send_keys(username) + email_field.send_keys(Keys.RETURN) + + # Wait for password field + time.sleep(2) # Google has animation between screens + password_field = wait.until( + EC.presence_of_element_located((By.CSS_SELECTOR, 'input[type="password"]')) + ) + password_field.send_keys(password) + password_field.send_keys(Keys.RETURN) + + return True + except Exception as e: + print(f"Auto-login failed: {e}") + return False + + +def extract_cookies(driver): + """Extract the required cookies from the browser.""" + # Navigate to a chat.google.com path to ensure we get the right cookies + driver.get("https://chat.google.com/u/0/mole/world") + + all_cookies = driver.get_cookies() + target_names = {"COMPASS", "SSID", "SID", "OSID", "HSID"} + extracted = {} + compass_cookie = None + + for cookie in all_cookies: + name_upper = cookie["name"].upper() + if name_upper not in target_names: + continue + if name_upper == "COMPASS": + if cookie.get("path", "") == "/": + compass_cookie = cookie + elif compass_cookie is None: + compass_cookie = cookie + else: + extracted[name_upper] = cookie["value"] + + if compass_cookie: + extracted["COMPASS"] = compass_cookie["value"] + + return extracted + + +def main(): + parser = argparse.ArgumentParser( + description="Extract Google Chat cookies for Mautrix bridge authentication" + ) + parser.add_argument( + "--entry", + default="google.com", + help="rbw entry name for Google credentials (default: google.com)", + ) + parser.add_argument( + "--copy", + action="store_true", + help="Copy the cookie JSON to clipboard", + ) + parser.add_argument( + "--manual", + action="store_true", + help="Skip auto-login, do manual login only", + ) + args = parser.parse_args() + + username = None + password = None + + # Get credentials from rbw if not manual mode + if not args.manual: + if not check_rbw_unlocked(): + print("rbw vault is locked. Please run: rbw unlock") + sys.exit(1) + + username, password = get_rbw_credentials(args.entry) + if not username or not password: + print(f"Could not get credentials from rbw entry '{args.entry}'") + print("Falling back to manual login...") + + # Configure Chrome options + options = webdriver.ChromeOptions() + options.add_argument("--incognito") + + # Initialize the Chrome driver + service = Service() + driver = webdriver.Chrome(service=service, options=options) + + try: + print("Opening https://chat.google.com ...") + driver.get("https://chat.google.com") + + if username and password and not args.manual: + print("Attempting automatic login...") + if automate_google_login(driver, username, password): + print("Credentials entered. Complete any 2FA if prompted.") + else: + print("Auto-login failed, please log in manually.") + + print("\nOnce logged in, press Enter to extract cookies...") + input() + + cookies = extract_cookies(driver) + json_data = json.dumps(cookies, indent=2) + + print("\nExtracted Cookie JSON:") + print(json_data) + + if args.copy: + if copy_to_clipboard(json_data): + print("\nCopied to clipboard!") + else: + print("\nFailed to copy to clipboard (wl-copy/xclip not found)") + + finally: + print("\nClosing browser...") + driver.quit() + + +if __name__ == "__main__": + main()