diff --git a/Auth/auth_flow.py b/Auth/auth_flow.py index 0f80292..eb6301f 100644 --- a/Auth/auth_flow.py +++ b/Auth/auth_flow.py @@ -4,7 +4,7 @@ # from selenium.webdriver.support.ui import WebDriverWait -from chrome_driver import create_driver +from chrome_driver import create_driver, safe_quit_driver def request_oauth_account_token_flow(): @@ -41,8 +41,8 @@ def request_oauth_account_token_flow(): return oauth_token_value finally: - # Close the browser - driver.quit() + # Close the browser safely + safe_quit_driver(driver) if __name__ == '__main__': request_oauth_account_token_flow() \ No newline at end of file diff --git a/Auth/fcm_receiver.py b/Auth/fcm_receiver.py index 65e97ee..2de78f7 100644 --- a/Auth/fcm_receiver.py +++ b/Auth/fcm_receiver.py @@ -35,6 +35,7 @@ def __init__(self): api_key=api_key, messaging_sender_id=message_sender_id, bundle_id="com.google.android.apps.adm", + android_cert_sha1="38918a453d07199354f8b19af05ec6562ced5788", ) self.credentials = get_cached_value('fcm_credentials') diff --git a/Auth/firebase_messaging/fcmregister.py b/Auth/firebase_messaging/fcmregister.py index 46e55ae..4b2fad0 100644 --- a/Auth/firebase_messaging/fcmregister.py +++ b/Auth/firebase_messaging/fcmregister.py @@ -66,6 +66,14 @@ _logger = logging.getLogger(__name__) +def _normalize_sha1_fingerprint(v: str) -> str: + """Strip colons/spaces and validate a SHA-1 hex fingerprint.""" + v = v.replace(":", "").replace(" ", "").strip().lower() + if len(v) != 40 or not all(c in "0123456789abcdef" for c in v): + raise ValueError(f"Invalid SHA-1 fingerprint: {v!r}") + return v + + @dataclass class FcmRegisterConfig: project_id: str @@ -78,10 +86,15 @@ class FcmRegisterConfig: vapid_key: str | None = GCM_SERVER_KEY_B64 persistend_ids: list[str] | None = None heartbeat_interval_ms: int = 5 * 60 * 1000 # 5 mins + android_cert_sha1: str | None = None def __postinit__(self) -> None: if self.persistend_ids is None: self.persistend_ids = [] + if self.android_cert_sha1 is not None: + self.android_cert_sha1 = _normalize_sha1_fingerprint( + self.android_cert_sha1 + ) class FcmRegister: @@ -105,6 +118,13 @@ def __init__( self._http_client_session = http_client_session self._local_session: ClientSession | None = None + def _add_android_restriction_headers(self, headers: dict[str, str]) -> None: + """Add X-Android-Package/Cert headers when configured.""" + if self.config.bundle_id: + headers["X-Android-Package"] = self.config.bundle_id + if self.config.android_cert_sha1: + headers["X-Android-Cert"] = self.config.android_cert_sha1 + def _get_checkin_payload( self, android_id: int | None = None, security_token: int | None = None ) -> AndroidCheckinRequest: @@ -309,6 +329,7 @@ async def fcm_install(self) -> dict | None: "x-firebase-client": hb_header, "x-goog-api-key": self.config.api_key, } + self._add_android_restriction_headers(headers) payload = { "appId": self.config.app_id, "authVersion": AUTH_VERSION, @@ -353,6 +374,7 @@ async def fcm_refresh_install_token(self) -> dict | None: "x-firebase-client": hb_header, "x-goog-api-key": self.config.api_key, } + self._add_android_restriction_headers(headers) payload = { "installation": { "sdkVersion": SDK_VERSION, @@ -417,6 +439,7 @@ async def fcm_register( "x-goog-api-key": self.config.api_key, "x-goog-firebase-installations-auth": installation["token"], } + self._add_android_restriction_headers(headers) # If vapid_key is the default do not send it here or it will error vapid_key = ( self.config.vapid_key diff --git a/KeyBackup/shared_key_retrieval.py b/KeyBackup/shared_key_retrieval.py index 8759133..727c68d 100644 --- a/KeyBackup/shared_key_retrieval.py +++ b/KeyBackup/shared_key_retrieval.py @@ -10,7 +10,7 @@ def _retrieve_shared_key(): print("""[SharedKeyRetrieval] You need to log in again to access end-to-end encrypted keys to decrypt location reports. -> This script will now open Google Chrome on your device. +> This script will now open Google Chrome on your device. > Make that you allow Python (or PyCharm) to control Chrome (macOS only). """) @@ -19,6 +19,12 @@ def _retrieve_shared_key(): shared_key = request_shared_key_flow() + if not shared_key: + raise RuntimeError( + "Shared key retrieval failed. Ensure Chrome/Chromium is installed " + "and that you complete the Google sign-in flow." + ) + return shared_key diff --git a/NovaApi/ListDevices/nbe_list_devices.py b/NovaApi/ListDevices/nbe_list_devices.py index 7590f31..8f153e8 100644 --- a/NovaApi/ListDevices/nbe_list_devices.py +++ b/NovaApi/ListDevices/nbe_list_devices.py @@ -47,6 +47,16 @@ def list_devices(): device_list = parse_device_list_protobuf(result_hex) refresh_custom_trackers(device_list) + + # Auto-retrieve shared_key so secrets.json is complete before export + try: + from KeyBackup.shared_key_retrieval import get_shared_key + get_shared_key() + print("[SharedKey] Shared key cached successfully.") + except Exception as e: + print(f"[SharedKey] Warning: Could not retrieve shared key: {e}") + print("[SharedKey] You can retry later by locating a device.") + canonic_ids = get_canonic_ids(device_list) print("") diff --git a/chrome_driver.py b/chrome_driver.py index 6d71ec2..9a19306 100644 --- a/chrome_driver.py +++ b/chrome_driver.py @@ -7,92 +7,269 @@ import shutil import platform import time +import subprocess +import re + +def get_chrome_version(chrome_path): + """Get Chrome version from executable.""" + try: + if platform.system() == "Windows": + # Try to get version from registry first + try: + import winreg + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Google\Chrome\BLBeacon") + version, _ = winreg.QueryValueEx(key, "version") + winreg.CloseKey(key) + return int(version.split('.')[0]) + except: + pass + # Fallback: run chrome with --version + result = subprocess.run([chrome_path, "--version"], capture_output=True, text=True, timeout=10) + version_match = re.search(r'(\d+)\.\d+\.\d+\.\d+', result.stdout) + if version_match: + return int(version_match.group(1)) + else: + result = subprocess.run([chrome_path, "--version"], capture_output=True, text=True, timeout=10) + version_match = re.search(r'(\d+)\.\d+\.\d+\.\d+', result.stdout) + if version_match: + return int(version_match.group(1)) + except Exception as e: + print(f"[ChromeDriver] Could not determine Chrome version: {e}") + return None def find_chrome(): """Find Chrome executable using known paths and system commands.""" + # Expand %USERNAME% for Windows paths + username = os.environ.get('USERNAME', os.environ.get('USER', '')) + possiblePaths = [ + # Windows paths r"C:\Program Files\Google\Chrome\Application\chrome.exe", r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", r"C:\ProgramData\chocolatey\bin\chrome.exe", - r"C:\Users\%USERNAME%\AppData\Local\Google\Chrome\Application\chrome.exe", + os.path.expandvars(r"C:\Users\%USERNAME%\AppData\Local\Google\Chrome\Application\chrome.exe"), + f"C:\\Users\\{username}\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe", + # Additional Windows paths for Chrome installed per-user + os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Google', 'Chrome', 'Application', 'chrome.exe'), + os.path.join(os.environ.get('PROGRAMFILES', ''), 'Google', 'Chrome', 'Application', 'chrome.exe'), + os.path.join(os.environ.get('PROGRAMFILES(X86)', ''), 'Google', 'Chrome', 'Application', 'chrome.exe'), + # Linux paths "/usr/bin/google-chrome", + "/usr/bin/google-chrome-stable", "/usr/local/bin/google-chrome", "/opt/google/chrome/chrome", "/snap/bin/chromium", - "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + # macOS paths + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + os.path.expanduser("~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"), ] - # Check predefined paths + + # Filter out empty paths and check for existence for path in possiblePaths: - if os.path.exists(path): + if path and os.path.exists(path): + print(f"[ChromeDriver] Found Chrome at: {path}") return path + # Use system command to find Chrome try: if platform.system() == "Windows": - chrome_path = shutil.which("chrome") + # Try multiple executable names on Windows + for name in ["chrome", "google-chrome", "chromium"]: + chrome_path = shutil.which(name) + if chrome_path: + return chrome_path + # Try using 'where' command on Windows + try: + result = subprocess.run(["where", "chrome.exe"], capture_output=True, text=True, timeout=5) + if result.returncode == 0 and result.stdout.strip(): + return result.stdout.strip().split('\n')[0] + except: + pass else: - chrome_path = shutil.which("google-chrome") or shutil.which("chromium") - if chrome_path: - return chrome_path + for name in ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"]: + chrome_path = shutil.which(name) + if chrome_path: + return chrome_path except Exception as e: print(f"[ChromeDriver] Error while searching system paths: {e}") return None -def get_options(): +def get_options(headless=False): + """Configure Chrome options for undetected-chromedriver.""" chrome_options = uc.ChromeOptions() chrome_options.add_argument("--start-maximized") chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") + chrome_options.add_argument("--disable-gpu") + chrome_options.add_argument("--disable-extensions") + chrome_options.add_argument("--disable-infobars") + chrome_options.add_argument("--ignore-certificate-errors") + + if headless: + chrome_options.add_argument("--headless=new") + return chrome_options -def create_driver(): - """Create a Chrome WebDriver with undetected_chromedriver.""" + +def _try_create_uc_driver(chrome_options, version_main=None, browser_executable_path=None): + """Helper to create undetected-chromedriver with proper error handling.""" + kwargs = {"options": chrome_options} + + if version_main is not None: + kwargs["version_main"] = version_main + + if browser_executable_path is not None: + kwargs["browser_executable_path"] = browser_executable_path + + return uc.Chrome(**kwargs) + + +def _try_webdriver_manager_fallback(): + """Try to use webdriver-manager as a fallback for standard Selenium.""" + try: + from selenium import webdriver + from selenium.webdriver.chrome.service import Service + from webdriver_manager.chrome import ChromeDriverManager + + print("[ChromeDriver] Attempting webdriver-manager fallback...") + service = Service(ChromeDriverManager().install()) + options = webdriver.ChromeOptions() + options.add_argument("--start-maximized") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + + driver = webdriver.Chrome(service=service, options=options) + print("[ChromeDriver] Started using webdriver-manager (standard Selenium).") + print("[ChromeDriver] WARNING: This uses standard Selenium without bot detection bypass!") + return driver + except Exception as e: + print(f"[ChromeDriver] webdriver-manager fallback failed: {e}") + return None + + +def safe_quit_driver(driver): + """Safely quit the Chrome driver, handling WinError 6 and other errors.""" + if driver is None: + return + try: - # Kill any existing Chrome processes first + # Try normal quit first + driver.quit() + except OSError as e: + # Handle "WinError 6: The handle is invalid" and similar errors + print(f"[ChromeDriver] OSError during quit (usually harmless): {e}") + except Exception as e: + print(f"[ChromeDriver] Error during quit: {e}") + finally: + # Force kill any remaining processes try: if platform.system() == "Windows": - os.system("taskkill /f /im chrome.exe >nul 2>&1") + os.system("taskkill /f /im chromedriver.exe >nul 2>&1") else: - os.system("pkill -f chrome") - time.sleep(2) # Wait for processes to close + os.system("pkill -f chromedriver >/dev/null 2>&1") except: pass - + + +def create_driver(): + """Create a Chrome WebDriver with undetected_chromedriver. + + Attempts multiple strategies: + 1. Default undetected-chromedriver with auto-detection + 2. Specify Chrome path explicitly + 3. Specify Chrome version explicitly + 4. Try headless mode + 5. Fall back to webdriver-manager (standard Selenium) + """ + # Kill any existing Chrome processes first + try: + if platform.system() == "Windows": + os.system("taskkill /f /im chrome.exe >nul 2>&1") + os.system("taskkill /f /im chromedriver.exe >nul 2>&1") + else: + os.system("pkill -f chrome >/dev/null 2>&1") + os.system("pkill -f chromedriver >/dev/null 2>&1") + time.sleep(1) + except: + pass + + chrome_path = find_chrome() + version_main = None + + if chrome_path: + version_main = get_chrome_version(chrome_path) + if version_main: + print(f"[ChromeDriver] Detected Chrome version: {version_main}") + + # Strategy 1: Default with version_main if detected + try: chrome_options = get_options() - driver = uc.Chrome(options=chrome_options, version_main=None) + if chrome_path: + chrome_options.binary_location = chrome_path + driver = _try_create_uc_driver(chrome_options, version_main=version_main) print("[ChromeDriver] Installed and browser started.") return driver except Exception as e: - print(f"[ChromeDriver] Default ChromeDriver creation failed: {e}") - print("[ChromeDriver] Trying alternative paths...") - chrome_path = find_chrome() - if chrome_path: - chrome_options = get_options() - chrome_options.binary_location = chrome_path - try: - driver = uc.Chrome(options=chrome_options, version_main=None) - print(f"[ChromeDriver] ChromeDriver started using {chrome_path}") - return driver - except Exception as e: - print(f"[ChromeDriver] ChromeDriver failed using path {chrome_path}: {e}") - else: - print("[ChromeDriver] No Chrome executable found in known paths.") - - # Final fallback - try headless mode - print("[ChromeDriver] Trying headless mode as last resort...") + print(f"[ChromeDriver] Strategy 1 (default) failed: {e}") + + # Strategy 2: Use browser_executable_path parameter + if chrome_path: try: chrome_options = get_options() - chrome_options.add_argument("--headless") - driver = uc.Chrome(options=chrome_options, version_main=None) - print("[ChromeDriver] Started in headless mode successfully.") + driver = _try_create_uc_driver( + chrome_options, + version_main=version_main, + browser_executable_path=chrome_path + ) + print(f"[ChromeDriver] Started using browser_executable_path: {chrome_path}") return driver except Exception as e: - print(f"[ChromeDriver] Headless mode also failed: {e}") - - raise Exception( - "[ChromeDriver] Failed to install ChromeDriver. A current version of Chrome was not detected on your system.\n" - "If you know that Chrome is installed, update Chrome to the latest version. If the script is still not working, " - "set the path to your Chrome executable manually inside the script." - ) + print(f"[ChromeDriver] Strategy 2 (explicit path) failed: {e}") + + # Strategy 3: Try without specifying version + try: + chrome_options = get_options() + if chrome_path: + chrome_options.binary_location = chrome_path + driver = _try_create_uc_driver(chrome_options, version_main=None) + print("[ChromeDriver] Started without explicit version.") + return driver + except Exception as e: + print(f"[ChromeDriver] Strategy 3 (no version) failed: {e}") + + # Strategy 4: Try headless mode + print("[ChromeDriver] Trying headless mode...") + try: + chrome_options = get_options(headless=True) + if chrome_path: + chrome_options.binary_location = chrome_path + driver = _try_create_uc_driver(chrome_options, version_main=version_main) + print("[ChromeDriver] Started in headless mode successfully.") + return driver + except Exception as e: + print(f"[ChromeDriver] Strategy 4 (headless) failed: {e}") + + # Strategy 5: webdriver-manager fallback + driver = _try_webdriver_manager_fallback() + if driver: + return driver + + # All strategies failed + raise Exception( + "[ChromeDriver] Failed to start ChromeDriver after all attempts.\n" + "Possible solutions:\n" + "1. Make sure Google Chrome is installed and up-to-date\n" + "2. Try: pip install --upgrade undetected-chromedriver selenium webdriver-manager\n" + "3. If Chrome is installed but not detected, set the path manually:\n" + f" Current detected path: {chrome_path or 'None'}\n" + f" Current detected version: {version_main or 'Unknown'}\n" + "4. Check if Chrome is blocked by antivirus or firewall" + ) + if __name__ == '__main__': - create_driver() \ No newline at end of file + driver = create_driver() + print("Driver created successfully!") + driver.quit() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8ddccc4..852591c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ undetected-chromedriver>=3.5.5 selenium>=4.27.1 +webdriver-manager>=4.0.2 gpsoauth>=1.1.1 requests>=2.32.3 beautifulsoup4>=4.12.3