diff --git a/bot/scripts/stream-test/browse-search.mjs b/bot/scripts/stream-test/browse-search.mjs index 4fa5d65..e1fdbab 100644 --- a/bot/scripts/stream-test/browse-search.mjs +++ b/bot/scripts/stream-test/browse-search.mjs @@ -87,13 +87,26 @@ async function closeAll() { try { await connected?.close(); } catch { /* ignore */ } } +// Human-like search: land on the site's home page, type the query into its +// search box one key at a time, and press Enter — the way a person would, +// rather than jumping straight to a results URL. +async function typeSearch(homeUrl, boxSelector, query) { + await page.goto(homeUrl, { waitUntil: 'domcontentloaded' }); + const box = page.locator(boxSelector).first(); + await box.waitFor({ timeout: 15000 }); + await box.click(); + await box.pressSequentially(query, { delay: 45 }); + await box.press('Enter'); +} + try { await acquirePage(); page.setDefaultTimeout(20000); await page.bringToFront().catch(() => {}); if (mode === 'youtube') { - await page.goto(`https://www.youtube.com/results?search_query=${encodeURIComponent(query)}`, { waitUntil: 'domcontentloaded' }); + // Type into YouTube's search box like a person, then play the first result. + await typeSearch('https://www.youtube.com/?hl=ko', 'input#search, input[name="search_query"]', query); await page.waitForSelector('ytd-video-renderer a#video-title, a#video-title', { timeout: 20000 }); const first = page.locator('ytd-video-renderer a#video-title, a#video-title').first(); const title = (await first.getAttribute('title').catch(() => '')) || (await first.innerText().catch(() => '')); @@ -102,7 +115,9 @@ try { await page.evaluate(() => { const v = document.querySelector('video'); if (v && v.paused) v.play(); }); out({ ok: true, mode, title: (title || '').trim(), url: page.url() }); } else { - await page.goto(`https://www.google.com/search?q=${encodeURIComponent(query)}&hl=ko`, { waitUntil: 'domcontentloaded' }); + // Type into Google's search box like a person, then read the results. + await typeSearch('https://www.google.com/?hl=ko', 'textarea[name="q"], input[name="q"]', query); + await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(1500); // Google serves its bot-detection interstitial (/sorry/index) to sessions it // suspects are automated. Detect it structurally (by URL, locale-independent) diff --git a/src/jarvis/tools/builtin/web_search.spec.md b/src/jarvis/tools/builtin/web_search.spec.md index 2e7915a..026a97f 100644 --- a/src/jarvis/tools/builtin/web_search.spec.md +++ b/src/jarvis/tools/builtin/web_search.spec.md @@ -22,7 +22,10 @@ path (evals, text entry) and falls back to the master flag: - **on-screen Chrome**: `browser_search()` drives Chrome (Node CDP helper `bot/scripts/stream-test/browse-search.mjs`) to Google-search the query, so - the action is visible on the Go-Live broadcast. When no broadcast Chrome is + the action is visible on the Go-Live broadcast. The helper searches the + human way — it loads the site home page, types the query into the search box + one key at a time, and presses Enter (both Google `search` and `youtube`), + rather than jumping to a results URL. When no broadcast Chrome is reachable on CDP (e.g. a plain text turn with no active broadcast), the helper falls back, for `search` only, to launching its own Chrome so browser-based Google search still works with no API cost. Fallback order: