Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Query Google NotebookLM notebooks from Claude Code for source-grounded, citation-backed answers from Gemini.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/auth_manager.py
1#!/usr/bin/env python32"""3Authentication Manager for NotebookLM4Handles Google login and browser state persistence5Based on the MCP server implementation67Implements hybrid auth approach:8- Persistent browser profile (user_data_dir) for fingerprint consistency9- Manual cookie injection from state.json for session cookies (Playwright bug workaround)10See: https://github.com/microsoft/playwright/issues/3613911"""1213import json14import time15import argparse16import shutil17import re18import sys19from pathlib import Path20from typing import Optional, Dict, Any2122from patchright.sync_api import sync_playwright, BrowserContext2324# Add parent directory to path25sys.path.insert(0, str(Path(__file__).parent))2627from config import BROWSER_STATE_DIR, STATE_FILE, AUTH_INFO_FILE, DATA_DIR28from browser_utils import BrowserFactory293031class AuthManager:32"""33Manages authentication and browser state for NotebookLM3435Features:36- Interactive Google login37- Browser state persistence38- Session restoration39- Account switching40"""4142def __init__(self):43"""Initialize the authentication manager"""44# Ensure directories exist45DATA_DIR.mkdir(parents=True, exist_ok=True)46BROWSER_STATE_DIR.mkdir(parents=True, exist_ok=True)4748self.state_file = STATE_FILE49self.auth_info_file = AUTH_INFO_FILE50self.browser_state_dir = BROWSER_STATE_DIR5152def is_authenticated(self) -> bool:53"""Check if valid authentication exists"""54if not self.state_file.exists():55return False5657# Check if state file is not too old (7 days)58age_days = (time.time() - self.state_file.stat().st_mtime) / 8640059if age_days > 7:60print(f"⚠️ Browser state is {age_days:.1f} days old, may need re-authentication")6162return True6364def get_auth_info(self) -> Dict[str, Any]:65"""Get authentication information"""66info = {67'authenticated': self.is_authenticated(),68'state_file': str(self.state_file),69'state_exists': self.state_file.exists()70}7172if self.auth_info_file.exists():73try:74with open(self.auth_info_file, 'r') as f:75saved_info = json.load(f)76info.update(saved_info)77except Exception:78pass7980if info['state_exists']:81age_hours = (time.time() - self.state_file.stat().st_mtime) / 360082info['state_age_hours'] = age_hours8384return info8586def setup_auth(self, headless: bool = False, timeout_minutes: int = 10) -> bool:87"""88Perform interactive authentication setup8990Args:91headless: Run browser in headless mode (False for login)92timeout_minutes: Maximum time to wait for login9394Returns:95True if authentication successful96"""97print("🔐 Starting authentication setup...")98print(f" Timeout: {timeout_minutes} minutes")99100playwright = None101context = None102103try:104playwright = sync_playwright().start()105106# Launch using factory107context = BrowserFactory.launch_persistent_context(108playwright,109headless=headless110)111112# Navigate to NotebookLM113page = context.new_page()114page.goto("https://notebooklm.google.com", wait_until="domcontentloaded")115116# Check if already authenticated117if "notebooklm.google.com" in page.url and "accounts.google.com" not in page.url:118print(" ✅ Already authenticated!")119self._save_browser_state(context)120return True121122# Wait for manual login123print("\n ⏳ Please log in to your Google account...")124print(f" ⏱️ Waiting up to {timeout_minutes} minutes for login...")125126try:127# Wait for URL to change to NotebookLM (regex ensures it's the actual domain, not a parameter)128timeout_ms = int(timeout_minutes * 60 * 1000)129page.wait_for_url(re.compile(r"^https://notebooklm\.google\.com/"), timeout=timeout_ms)130131print(f" ✅ Login successful!")132133# Save authentication state134self._save_browser_state(context)135self._save_auth_info()136return True137138except Exception as e:139print(f" ❌ Authentication timeout: {e}")140return False141142except Exception as e:143print(f" ❌ Error: {e}")144return False145146finally:147# Clean up browser resources148if context:149try:150context.close()151except Exception:152pass153154if playwright:155try:156playwright.stop()157except Exception:158pass159160def _save_browser_state(self, context: BrowserContext):161"""Save browser state to disk"""162try:163# Save storage state (cookies, localStorage)164context.storage_state(path=str(self.state_file))165print(f" 💾 Saved browser state to: {self.state_file}")166except Exception as e:167print(f" ❌ Failed to save browser state: {e}")168raise169170def _save_auth_info(self):171"""Save authentication metadata"""172try:173info = {174'authenticated_at': time.time(),175'authenticated_at_iso': time.strftime('%Y-%m-%d %H:%M:%S')176}177with open(self.auth_info_file, 'w') as f:178json.dump(info, f, indent=2)179except Exception:180pass # Non-critical181182def clear_auth(self) -> bool:183"""184Clear all authentication data185186Returns:187True if cleared successfully188"""189print("🗑️ Clearing authentication data...")190191try:192# Remove browser state193if self.state_file.exists():194self.state_file.unlink()195print(" ✅ Removed browser state")196197# Remove auth info198if self.auth_info_file.exists():199self.auth_info_file.unlink()200print(" ✅ Removed auth info")201202# Clear entire browser state directory203if self.browser_state_dir.exists():204shutil.rmtree(self.browser_state_dir)205self.browser_state_dir.mkdir(parents=True, exist_ok=True)206print(" ✅ Cleared browser data")207208return True209210except Exception as e:211print(f" ❌ Error clearing auth: {e}")212return False213214def re_auth(self, headless: bool = False, timeout_minutes: int = 10) -> bool:215"""216Perform re-authentication (clear and setup)217218Args:219headless: Run browser in headless mode220timeout_minutes: Login timeout in minutes221222Returns:223True if successful224"""225print("🔄 Starting re-authentication...")226227# Clear existing auth228self.clear_auth()229230# Setup new auth231return self.setup_auth(headless, timeout_minutes)232233def validate_auth(self) -> bool:234"""235Validate that stored authentication works236Uses persistent context to match actual usage pattern237238Returns:239True if authentication is valid240"""241if not self.is_authenticated():242return False243244print("🔍 Validating authentication...")245246playwright = None247context = None248249try:250playwright = sync_playwright().start()251252# Launch using factory253context = BrowserFactory.launch_persistent_context(254playwright,255headless=True256)257258# Try to access NotebookLM259page = context.new_page()260page.goto("https://notebooklm.google.com", wait_until="domcontentloaded", timeout=30000)261262# Check if we can access NotebookLM263if "notebooklm.google.com" in page.url and "accounts.google.com" not in page.url:264print(" ✅ Authentication is valid")265return True266else:267print(" ❌ Authentication is invalid (redirected to login)")268return False269270except Exception as e:271print(f" ❌ Validation failed: {e}")272return False273274finally:275if context:276try:277context.close()278except Exception:279pass280if playwright:281try:282playwright.stop()283except Exception:284pass285286287def main():288"""Command-line interface for authentication management"""289parser = argparse.ArgumentParser(description='Manage NotebookLM authentication')290291subparsers = parser.add_subparsers(dest='command', help='Commands')292293# Setup command294setup_parser = subparsers.add_parser('setup', help='Setup authentication')295setup_parser.add_argument('--headless', action='store_true', help='Run in headless mode')296setup_parser.add_argument('--timeout', type=float, default=10, help='Login timeout in minutes (default: 10)')297298# Status command299subparsers.add_parser('status', help='Check authentication status')300301# Validate command302subparsers.add_parser('validate', help='Validate authentication')303304# Clear command305subparsers.add_parser('clear', help='Clear authentication')306307# Re-auth command308reauth_parser = subparsers.add_parser('reauth', help='Re-authenticate (clear + setup)')309reauth_parser.add_argument('--timeout', type=float, default=10, help='Login timeout in minutes (default: 10)')310311args = parser.parse_args()312313# Initialize manager314auth = AuthManager()315316# Execute command317if args.command == 'setup':318if auth.setup_auth(headless=args.headless, timeout_minutes=args.timeout):319print("\n✅ Authentication setup complete!")320print("You can now use ask_question.py to query NotebookLM")321else:322print("\n❌ Authentication setup failed")323exit(1)324325elif args.command == 'status':326info = auth.get_auth_info()327print("\n🔐 Authentication Status:")328print(f" Authenticated: {'Yes' if info['authenticated'] else 'No'}")329if info.get('state_age_hours'):330print(f" State age: {info['state_age_hours']:.1f} hours")331if info.get('authenticated_at_iso'):332print(f" Last auth: {info['authenticated_at_iso']}")333print(f" State file: {info['state_file']}")334335elif args.command == 'validate':336if auth.validate_auth():337print("Authentication is valid and working")338else:339print("Authentication is invalid or expired")340print("Run: auth_manager.py setup")341342elif args.command == 'clear':343if auth.clear_auth():344print("Authentication cleared")345346elif args.command == 'reauth':347if auth.re_auth(timeout_minutes=args.timeout):348print("\n✅ Re-authentication complete!")349else:350print("\n❌ Re-authentication failed")351exit(1)352353else:354parser.print_help()355356357if __name__ == "__main__":358main()