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/browser_utils.py
1"""2Browser Utilities for NotebookLM Skill3Handles browser launching, stealth features, and common interactions4"""56import json7import time8import random9from typing import Optional, List1011from patchright.sync_api import Playwright, BrowserContext, Page12from config import BROWSER_PROFILE_DIR, STATE_FILE, BROWSER_ARGS, USER_AGENT131415class BrowserFactory:16"""Factory for creating configured browser contexts"""1718@staticmethod19def launch_persistent_context(20playwright: Playwright,21headless: bool = True,22user_data_dir: str = str(BROWSER_PROFILE_DIR)23) -> BrowserContext:24"""25Launch a persistent browser context with anti-detection features26and cookie workaround.27"""28# Launch persistent context29context = playwright.chromium.launch_persistent_context(30user_data_dir=user_data_dir,31channel="chrome", # Use real Chrome32headless=headless,33no_viewport=True,34ignore_default_args=["--enable-automation"],35user_agent=USER_AGENT,36args=BROWSER_ARGS37)3839# Cookie Workaround for Playwright bug #3613940# Session cookies (expires=-1) don't persist in user_data_dir automatically41BrowserFactory._inject_cookies(context)4243return context4445@staticmethod46def _inject_cookies(context: BrowserContext):47"""Inject cookies from state.json if available"""48if STATE_FILE.exists():49try:50with open(STATE_FILE, 'r') as f:51state = json.load(f)52if 'cookies' in state and len(state['cookies']) > 0:53context.add_cookies(state['cookies'])54# print(f" 🔧 Injected {len(state['cookies'])} cookies from state.json")55except Exception as e:56print(f" ⚠️ Could not load state.json: {e}")575859class StealthUtils:60"""Human-like interaction utilities"""6162@staticmethod63def random_delay(min_ms: int = 100, max_ms: int = 500):64"""Add random delay"""65time.sleep(random.uniform(min_ms / 1000, max_ms / 1000))6667@staticmethod68def human_type(page: Page, selector: str, text: str, wpm_min: int = 320, wpm_max: int = 480):69"""Type with human-like speed"""70element = page.query_selector(selector)71if not element:72# Try waiting if not immediately found73try:74element = page.wait_for_selector(selector, timeout=2000)75except:76pass7778if not element:79print(f"⚠️ Element not found for typing: {selector}")80return8182# Click to focus83element.click()8485# Type86for char in text:87element.type(char, delay=random.uniform(25, 75))88if random.random() < 0.05:89time.sleep(random.uniform(0.15, 0.4))9091@staticmethod92def realistic_click(page: Page, selector: str):93"""Click with realistic movement"""94element = page.query_selector(selector)95if not element:96return9798# Optional: Move mouse to element (simplified)99box = element.bounding_box()100if box:101x = box['x'] + box['width'] / 2102y = box['y'] + box['height'] / 2103page.mouse.move(x, y, steps=5)104105StealthUtils.random_delay(100, 300)106element.click()107StealthUtils.random_delay(100, 300)108