Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Fetch any URL via Chrome CDP and convert the rendered page to clean markdown with YouTube transcript support.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/lib/browser/interaction-gates.ts
1import type { WaitForInteractionRequest } from "../adapters/types";2import type { BrowserSession } from "./session";34interface GateSnapshot {5title: string;6currentUrl: string;7bodyText: string;8hasCloudflareTurnstile: boolean;9hasCloudflareChallenge: boolean;10hasRecaptcha: boolean;11hasRecaptchaIframe: boolean;12hasHcaptcha: boolean;13hasHcaptchaIframe: boolean;14}1516export function detectInteractionGateFromSnapshot(snapshot: GateSnapshot): WaitForInteractionRequest | null {17const text = snapshot.bodyText.toLowerCase();18const title = snapshot.title.toLowerCase();19const url = snapshot.currentUrl.toLowerCase();2021if (22snapshot.hasCloudflareTurnstile ||23snapshot.hasCloudflareChallenge ||24title.includes("just a moment") ||25text.includes("verify you are human") ||26text.includes("checking your browser before accessing") ||27text.includes("enable javascript and cookies to continue") ||28url.includes("/cdn-cgi/challenge-platform/")29) {30return {31type: "wait_for_interaction",32kind: "cloudflare",33provider: "cloudflare",34reason: "Cloudflare human verification detected",35prompt: "Please complete the Cloudflare verification in the opened Chrome window. Extraction will continue automatically once the challenge disappears.",36requiresVisibleBrowser: true,37};38}3940if (41snapshot.hasRecaptcha ||42snapshot.hasRecaptchaIframe ||43text.includes("i'm not a robot") ||44text.includes("recaptcha")45) {46return {47type: "wait_for_interaction",48kind: "recaptcha",49provider: "google_recaptcha",50reason: "Google reCAPTCHA detected",51prompt: "Please complete the reCAPTCHA verification in the opened Chrome window. Extraction will continue automatically once the challenge disappears.",52requiresVisibleBrowser: true,53};54}5556if (57snapshot.hasHcaptcha ||58snapshot.hasHcaptchaIframe ||59text.includes("hcaptcha")60) {61return {62type: "wait_for_interaction",63kind: "hcaptcha",64provider: "hcaptcha",65reason: "hCaptcha verification detected",66prompt: "Please complete the hCaptcha verification in the opened Chrome window. Extraction will continue automatically once the challenge disappears.",67requiresVisibleBrowser: true,68};69}7071return null;72}7374export async function detectInteractionGate(browser: BrowserSession): Promise<WaitForInteractionRequest | null> {75const snapshot = await browser.evaluate<GateSnapshot>(`76(() => {77const bodyText = (document.body?.innerText ?? "").slice(0, 4000);78return {79title: document.title ?? "",80currentUrl: window.location.href,81bodyText,82hasCloudflareTurnstile: Boolean(83document.querySelector(84'.cf-turnstile, [name="cf-turnstile-response"], iframe[src*="challenges.cloudflare.com"]'85)86),87hasCloudflareChallenge: Boolean(88document.querySelector(89'#challenge-running, #cf-challenge-running, .challenge-platform, [data-ray], [data-translate="checking_browser"]'90)91),92hasRecaptcha: Boolean(93document.querySelector(94'.g-recaptcha, textarea[name="g-recaptcha-response"], iframe[title*="reCAPTCHA"]'95)96),97hasRecaptchaIframe: Boolean(98document.querySelector('iframe[src*="google.com/recaptcha"], iframe[src*="recaptcha/api2"]')99),100hasHcaptcha: Boolean(101document.querySelector(102'.h-captcha, textarea[name="h-captcha-response"], iframe[title*="hCaptcha"]'103)104),105hasHcaptchaIframe: Boolean(106document.querySelector('iframe[src*="hcaptcha.com"]')107),108};109})()110`).catch(() => ({111title: "",112currentUrl: "",113bodyText: "",114hasCloudflareTurnstile: false,115hasCloudflareChallenge: false,116hasRecaptcha: false,117hasRecaptchaIframe: false,118hasHcaptcha: false,119hasHcaptchaIframe: false,120}));121122return detectInteractionGateFromSnapshot(snapshot);123}124