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/ask_question.py
1#!/usr/bin/env python32"""3Simple NotebookLM Question Interface4Based on MCP server implementation - simplified without sessions56Implements hybrid auth approach:7- Persistent browser profile (user_data_dir) for fingerprint consistency8- Manual cookie injection from state.json for session cookies (Playwright bug workaround)9See: https://github.com/microsoft/playwright/issues/3613910"""1112import argparse13import sys14import time15import re16from pathlib import Path1718from patchright.sync_api import sync_playwright1920# Add parent directory to path21sys.path.insert(0, str(Path(__file__).parent))2223from auth_manager import AuthManager24from notebook_manager import NotebookLibrary25from config import QUERY_INPUT_SELECTORS, RESPONSE_SELECTORS26from browser_utils import BrowserFactory, StealthUtils272829# Follow-up reminder (adapted from MCP server for stateless operation)30# Since we don't have persistent sessions, we encourage comprehensive questions31FOLLOW_UP_REMINDER = (32"\n\nEXTREMELY IMPORTANT: Is that ALL you need to know? "33"You can always ask another question! Think about it carefully: "34"before you reply to the user, review their original request and this answer. "35"If anything is still unclear or missing, ask me another comprehensive question "36"that includes all necessary context (since each question opens a new browser session)."37)383940def ask_notebooklm(question: str, notebook_url: str, headless: bool = True) -> str:41"""42Ask a question to NotebookLM4344Args:45question: Question to ask46notebook_url: NotebookLM notebook URL47headless: Run browser in headless mode4849Returns:50Answer text from NotebookLM51"""52auth = AuthManager()5354if not auth.is_authenticated():55print("โ ๏ธ Not authenticated. Run: python auth_manager.py setup")56return None5758print(f"๐ฌ Asking: {question}")59print(f"๐ Notebook: {notebook_url}")6061playwright = None62context = None6364try:65# Start playwright66playwright = sync_playwright().start()6768# Launch persistent browser context using factory69context = BrowserFactory.launch_persistent_context(70playwright,71headless=headless72)7374# Navigate to notebook75page = context.new_page()76print(" ๐ Opening notebook...")77page.goto(notebook_url, wait_until="domcontentloaded")7879# Wait for NotebookLM80page.wait_for_url(re.compile(r"^https://notebooklm\.google\.com/"), timeout=10000)8182# Wait for query input (MCP approach)83print(" โณ Waiting for query input...")84query_element = None8586for selector in QUERY_INPUT_SELECTORS:87try:88query_element = page.wait_for_selector(89selector,90timeout=10000,91state="visible" # Only check visibility, not disabled!92)93if query_element:94print(f" โ Found input: {selector}")95break96except:97continue9899if not query_element:100print(" โ Could not find query input")101return None102103# Type question (human-like, fast)104print(" โณ Typing question...")105106# Use primary selector for typing107input_selector = QUERY_INPUT_SELECTORS[0]108StealthUtils.human_type(page, input_selector, question)109110# Submit111print(" ๐ค Submitting...")112page.keyboard.press("Enter")113114# Small pause115StealthUtils.random_delay(500, 1500)116117# Wait for response (MCP approach: poll for stable text)118print(" โณ Waiting for answer...")119120answer = None121stable_count = 0122last_text = None123deadline = time.time() + 120 # 2 minutes timeout124125while time.time() < deadline:126# Check if NotebookLM is still thinking (most reliable indicator)127try:128thinking_element = page.query_selector('div.thinking-message')129if thinking_element and thinking_element.is_visible():130time.sleep(1)131continue132except:133pass134135# Try to find response with MCP selectors136for selector in RESPONSE_SELECTORS:137try:138elements = page.query_selector_all(selector)139if elements:140# Get last (newest) response141latest = elements[-1]142text = latest.inner_text().strip()143144if text:145if text == last_text:146stable_count += 1147if stable_count >= 3: # Stable for 3 polls148answer = text149break150else:151stable_count = 0152last_text = text153except:154continue155156if answer:157break158159time.sleep(1)160161if not answer:162print(" โ Timeout waiting for answer")163return None164165print(" โ Got answer!")166# Add follow-up reminder to encourage Claude to ask more questions167return answer + FOLLOW_UP_REMINDER168169except Exception as e:170print(f" โ Error: {e}")171import traceback172traceback.print_exc()173return None174175finally:176# Always clean up177if context:178try:179context.close()180except:181pass182183if playwright:184try:185playwright.stop()186except:187pass188189190def main():191parser = argparse.ArgumentParser(description='Ask NotebookLM a question')192193parser.add_argument('--question', required=True, help='Question to ask')194parser.add_argument('--notebook-url', help='NotebookLM notebook URL')195parser.add_argument('--notebook-id', help='Notebook ID from library')196parser.add_argument('--show-browser', action='store_true', help='Show browser')197198args = parser.parse_args()199200# Resolve notebook URL201notebook_url = args.notebook_url202203if not notebook_url and args.notebook_id:204library = NotebookLibrary()205notebook = library.get_notebook(args.notebook_id)206if notebook:207notebook_url = notebook['url']208else:209print(f"โ Notebook '{args.notebook_id}' not found")210return 1211212if not notebook_url:213# Check for active notebook first214library = NotebookLibrary()215active = library.get_active_notebook()216if active:217notebook_url = active['url']218print(f"๐ Using active notebook: {active['name']}")219else:220# Show available notebooks221notebooks = library.list_notebooks()222if notebooks:223print("\n๐ Available notebooks:")224for nb in notebooks:225mark = " [ACTIVE]" if nb.get('id') == library.active_notebook_id else ""226print(f" {nb['id']}: {nb['name']}{mark}")227print("\nSpecify with --notebook-id or set active:")228print("python scripts/run.py notebook_manager.py activate --id ID")229else:230print("โ No notebooks in library. Add one first:")231print("python scripts/run.py notebook_manager.py add --url URL --name NAME --description DESC --topics TOPICS")232return 1233234# Ask the question235answer = ask_notebooklm(236question=args.question,237notebook_url=notebook_url,238headless=not args.show_browser239)240241if answer:242print("\n" + "=" * 60)243print(f"Question: {args.question}")244print("=" * 60)245print()246print(answer)247print()248print("=" * 60)249return 0250else:251print("\nโ Failed to get answer")252return 1253254255if __name__ == "__main__":256sys.exit(main())257