entrox-bypass 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +248 -149
- package/dist/index.cjs +407 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +388 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/types/index.d.ts +84 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/llm.ts","../src/cloudinary.ts","../src/captchaSolve.ts","../src/login.ts"],"sourcesContent":["import { solveCaptchaFromPage } from \"./captchaSolve.js\";\nimport { automateLogin, LoginOptions, LoginResult } from \"./login.js\";\n\nexport type { CaptchaResult } from \"./llm.js\";\nexport type { LoginOptions, LoginResult } from \"./login.js\";\n\nexport { solveCaptchaFromPage };\nexport { automateLogin };\n\nexport default {\n solveCaptchaFromPage,\n automateLogin,\n};\n","import \"dotenv/config\";\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage } from \"@langchain/core/messages\";\n\ninterface CaptchaResult {\n text: string;\n description: string;\n}\n\nclass LlmError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LlmError';\n }\n}\n\nconst model = new ChatOpenAI({\n model: process.env.LLM_MODEL || \"google/gemma-4-26b-a4b-it\",\n temperature: 0,\n apiKey: process.env.OPENROUTER_API_KEY,\n configuration: {\n baseURL: \"https://openrouter.ai/api/v1\",\n },\n});\n\nconst getImageDetails = async function (image_url: string): Promise<CaptchaResult> {\n if (!process.env.OPENROUTER_API_KEY) {\n throw new LlmError('OPENROUTER_API_KEY environment variable is required');\n }\n\n const response = await model.invoke([\n new HumanMessage({\n content: [\n {\n type: \"text\",\n text: `You are a CAPTCHA solving assistant. Analyze the provided image and return the following in strict JSON format:\n- \"text\": The exact text or characters shown in the CAPTCHA\n- \"description\": A brief description of what appears in the image\n\nIf there is no text in the image, set \"text\" to \"no text found\".\n\nExample response:\n{\"text\": \"X7K9P\", \"description\": \"Black text on white background showing alphanumeric characters\"}\n\nReturn ONLY valid JSON with no markdown formatting.`,\n },\n {\n type: \"image_url\",\n image_url: {\n url: image_url,\n },\n },\n ],\n }),\n ]);\n\n const content = response.content as string | Array<any>;\n \n if (typeof content === 'string') {\n const jsonString = content\n .replace(/```json\\s*/i, '')\n .replace(/```\\s*$/, '')\n .trim();\n \n try {\n return JSON.parse(jsonString);\n } catch (parseError) {\n throw new LlmError(`Failed to parse LLM response as JSON: ${parseError}`);\n }\n }\n \n throw new LlmError('Unexpected LLM response format');\n};\n\nexport default getImageDetails;\nexport type { CaptchaResult };\nexport { LlmError };\n","import 'dotenv/config';\nimport { v2 as cloudinary } from 'cloudinary';\n\ninterface CloudinaryConfig {\n cloud_name: string;\n api_key: string;\n api_secret: string;\n}\n\nclass CloudinaryError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CloudinaryError';\n }\n}\n\nfunction validateConfig(config: CloudinaryConfig): void {\n const missing: string[] = [];\n \n if (!config.cloud_name) missing.push('CLOUDINARY_CLOUD_NAME');\n if (!config.api_key) missing.push('CLOUDINARY_API_KEY');\n if (!config.api_secret) missing.push('CLOUDINARY_API_SECRET');\n \n if (missing.length > 0) {\n throw new CloudinaryError(`Missing Cloudinary credentials: ${missing.join(', ')}`);\n }\n}\n\nexport interface UploadResult {\n secure_url: string;\n public_id: string;\n format: string;\n width: number;\n height: number;\n bytes: number;\n resource_type: string;\n original_filename: string;\n}\n\nexport function getConfig(): CloudinaryConfig {\n return {\n cloud_name: process.env.CLOUDINARY_CLOUD_NAME || '',\n api_key: process.env.CLOUDINARY_API_KEY || '',\n api_secret: process.env.CLOUDINARY_API_SECRET || '',\n };\n}\n\nconst config = getConfig();\nvalidateConfig(config);\n\ncloudinary.config(config);\n\nexport default cloudinary;\nexport { CloudinaryError };\n","import { Page } from \"playwright\";\nimport getImageDetails, { CaptchaResult } from \"./llm.js\";\nimport cloudinary from \"./cloudinary.js\";\n\ninterface ScreenshotError extends Error {\n code: string;\n}\n\nexport async function solveCaptchaFromPage(\n page: Page,\n captchaSelector: string = \"img.captcha-image\",\n outputScreenshotPath?: string\n): Promise<CaptchaResult> {\n const captchaImage = page.locator(captchaSelector);\n\n try {\n await captchaImage.waitFor({ state: \"visible\" });\n } catch (error) {\n throw new Error(`CAPTCHA element not found or not visible. Selector: ${captchaSelector}`);\n }\n\n if (outputScreenshotPath) {\n await captchaImage.screenshot({ path: outputScreenshotPath });\n }\n\n let imageData: { mimeType: string; base64: string };\n\n try {\n imageData = await captchaImage.evaluate(async (img: HTMLImageElement) => {\n const response = await fetch(img.src);\n\n if (!response.ok) {\n throw new Error(`Image fetch failed: ${response.status}`);\n }\n\n const blob = await response.blob();\n\n return new Promise<{ mimeType: string; base64: string }>((resolve, reject) => {\n const reader = new FileReader();\n\n reader.onload = () => {\n const result = reader.result as string;\n const base64 = result.split(\",\")[1];\n resolve({\n mimeType: blob.type || \"image/png\",\n base64,\n });\n };\n\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n });\n } catch (error) {\n throw new Error(`Failed to extract CAPTCHA image: ${error}`);\n }\n\n let uploadResult;\n\n try {\n uploadResult = await cloudinary.uploader.upload(\n `data:${imageData.mimeType};base64,${imageData.base64}`,\n {\n folder: \"playwright-captcha-images\",\n resource_type: \"image\",\n type: \"auth\",\n }\n );\n } catch (error) {\n throw new Error(`Failed to upload CAPTCHA image to Cloudinary: ${error}`);\n }\n\n const imageUrl = uploadResult.secure_url;\n\n try {\n const result = await getImageDetails(imageUrl);\n return result;\n } catch (error) {\n throw new Error(`Failed to solve CAPTCHA using AI: ${error}`);\n }\n}\n","import { chromium, Browser, Page, BrowserContext } from \"playwright\";\nimport { solveCaptchaFromPage } from \"./captchaSolve.js\";\n\nexport interface LoginOptions {\n url: string;\n loginSelector?: string;\n passwordSelector?: string;\n captchaInputSelector?: string;\n submitButtonSelector?: string;\n headless?: boolean;\n outputScreenshotPath?: string;\n screenshotDelay?: number;\n}\n\nexport interface LoginResult {\n success: boolean;\n url: string;\n title: string;\n page: Page;\n error?: string;\n}\n\nclass BrowserError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BrowserError';\n }\n}\n\nasync function validateEnv(): Promise<void> {\n const missing: string[] = [];\n \n if (!process.env.EMAIL || process.env.EMAIL === 'dev@example.com') {\n missing.push('EMAIL - Set to actual email address');\n }\n if (!process.env.PASSWORD || process.env.PASSWORD === '22222222') {\n missing.push('PASSWORD - Set to actual password');\n }\n \n if (missing.length > 0) {\n console.warn('Warning: The following environment variables are set to defaults or missing:');\n missing.forEach(m => console.warn(` - ${m}`));\n console.warn('Please update your .env file with real credentials.');\n }\n}\n\nexport async function automateLogin(options: LoginOptions): Promise<LoginResult> {\n let browser: Browser | null = null;\n let context: BrowserContext | null = null;\n let page: Page | null = null;\n\n try {\n await validateEnv();\n\n browser = await chromium.launch({\n headless: options.headless ?? false,\n });\n\n context = await browser.newContext();\n page = await context.newPage();\n\n await page.goto(options.url, { waitUntil: \"domcontentloaded\" });\n\n const loginSelector = options.loginSelector || \"#login\";\n const passwordSelector = options.passwordSelector || \"#password\";\n const captchaSelector = \"img.captcha-image\";\n const captchaInputSelector = options.captchaInputSelector || \"#captcha\";\n const submitButtonSelector = options.submitButtonSelector || \"button[type='submit']\";\n\n await page.locator(loginSelector).fill(process.env.EMAIL || \"\");\n await page.locator(passwordSelector).fill(process.env.PASSWORD || \"\");\n\n const captchaResult = await solveCaptchaFromPage(\n page,\n captchaSelector,\n options.outputScreenshotPath ? undefined : undefined\n );\n\n await page.locator(captchaInputSelector).fill(captchaResult.text);\n await page.locator(submitButtonSelector).click();\n\n try {\n await page.waitForLoadState(\"domcontentloaded\", { timeout: 15000 });\n \n if (options.screenshotDelay) {\n await page.waitForTimeout(options.screenshotDelay);\n }\n\n if (options.outputScreenshotPath) {\n await page.screenshot({ path: options.outputScreenshotPath, fullPage: true });\n }\n } catch (error) {\n console.warn('Warning: Could not wait for post-login state:', error);\n }\n\n return {\n success: true,\n url: page.url(),\n title: await page.title(),\n page,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n \n let pageTitle = '';\n if (page) {\n try {\n pageTitle = await page.title();\n } catch {\n pageTitle = 'failed to get title';\n }\n }\n \n return {\n success: false,\n url: page?.url() || '',\n title: pageTitle,\n page: page!,\n error: errorMessage,\n };\n } finally {\n if (browser && !options.headless) {\n await browser.close().catch(() => {});\n }\n }\n}\n"],"mappings":"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","names":["import_config","config","cloudinary"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/llm.ts","../src/cloudinary.ts","../src/utils/retry.ts","../src/captchaSolve.ts","../src/login.ts","../src/utils/humanDelay.ts"],"sourcesContent":["import { solveCaptchaFromPage, SolveCaptchaOptions } from \"./captchaSolve.js\";\nimport {\n automateLogin,\n LoginOptions,\n LoginResult,\n sendWebhook,\n saveCookies,\n loadCookies,\n setupProxy,\n} from \"./login.js\";\nimport {\n randomBetween,\n delay,\n executeWithRandomDelay,\n} from \"./utils/humanDelay.js\";\nimport { retry, isRetryable, RetryOptions, RetryableError } from \"./utils/retry.js\";\n\nexport type { LoginOptions, LoginResult } from \"./login.js\";\nexport type { CaptchaResult } from \"./llm.js\";\nexport type { SolveCaptchaOptions } from \"./captchaSolve.js\";\nexport type { RetryOptions, RetryableError } from \"./utils/retry.js\";\n\nexport { solveCaptchaFromPage };\nexport { automateLogin };\nexport { sendWebhook, saveCookies, loadCookies, setupProxy };\nexport { randomBetween, delay, executeWithRandomDelay };\nexport { retry, isRetryable };\n\nexport default {\n solveCaptchaFromPage,\n automateLogin,\n sendWebhook,\n saveCookies,\n loadCookies,\n setupProxy,\n randomBetween,\n delay,\n executeWithRandomDelay,\n retry,\n isRetryable,\n};\n","import \"dotenv/config\";\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { HumanMessage } from \"@langchain/core/messages\";\n\ninterface CaptchaResult {\n text: string;\n description: string;\n}\n\nclass LlmError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LlmError';\n }\n}\n\nconst model = new ChatOpenAI({\n model: process.env.LLM_MODEL || \"google/gemma-4-26b-a4b-it\",\n temperature: 0,\n apiKey: process.env.OPENROUTER_API_KEY,\n configuration: {\n baseURL: \"https://openrouter.ai/api/v1\",\n },\n});\n\nconst getImageDetails = async function (image_url: string): Promise<CaptchaResult> {\n if (!process.env.OPENROUTER_API_KEY) {\n throw new LlmError('OPENROUTER_API_KEY environment variable is required');\n }\n\n const response = await model.invoke([\n new HumanMessage({\n content: [\n {\n type: \"text\",\n text: `You are a CAPTCHA solving assistant. Analyze the provided image and return the following in strict JSON format:\n- \"text\": The exact text or characters shown in the CAPTCHA\n- \"description\": A brief description of what appears in the image\n\nIf there is no text in the image, set \"text\" to \"no text found\".\n\nExample response:\n{\"text\": \"X7K9P\", \"description\": \"Black text on white background showing alphanumeric characters\"}\n\nReturn ONLY valid JSON with no markdown formatting.`,\n },\n {\n type: \"image_url\",\n image_url: {\n url: image_url,\n },\n },\n ],\n }),\n ]);\n\n const content = response.content as string | Array<any>;\n \n if (typeof content === 'string') {\n const jsonString = content\n .replace(/```json\\s*/i, '')\n .replace(/```\\s*$/, '')\n .trim();\n \n try {\n return JSON.parse(jsonString);\n } catch (parseError) {\n throw new LlmError(`Failed to parse LLM response as JSON: ${parseError}`);\n }\n }\n \n throw new LlmError('Unexpected LLM response format');\n};\n\nexport default getImageDetails;\nexport type { CaptchaResult };\nexport { LlmError };\n","import 'dotenv/config';\nimport { v2 as cloudinary } from 'cloudinary';\n\ninterface CloudinaryConfig {\n cloud_name: string;\n api_key: string;\n api_secret: string;\n}\n\nclass CloudinaryError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CloudinaryError';\n }\n}\n\nfunction validateConfig(config: CloudinaryConfig): void {\n const missing: string[] = [];\n \n if (!config.cloud_name) missing.push('CLOUDINARY_CLOUD_NAME');\n if (!config.api_key) missing.push('CLOUDINARY_API_KEY');\n if (!config.api_secret) missing.push('CLOUDINARY_API_SECRET');\n \n if (missing.length > 0) {\n throw new CloudinaryError(`Missing Cloudinary credentials: ${missing.join(', ')}`);\n }\n}\n\nexport interface UploadResult {\n secure_url: string;\n public_id: string;\n format: string;\n width: number;\n height: number;\n bytes: number;\n resource_type: string;\n original_filename: string;\n}\n\nexport function getConfig(): CloudinaryConfig {\n return {\n cloud_name: process.env.CLOUDINARY_CLOUD_NAME || '',\n api_key: process.env.CLOUDINARY_API_KEY || '',\n api_secret: process.env.CLOUDINARY_API_SECRET || '',\n };\n}\n\nconst config = getConfig();\nvalidateConfig(config);\n\ncloudinary.config(config);\n\nexport default cloudinary;\nexport { CloudinaryError };\n","import { LlmError } from \"../llm.js\";\n\nexport interface RetryableError extends Error {\n name: string;\n retryable?: boolean;\n}\n\nconst RETRYABLE_ERROR_NAMES = new Set([\n \"TimeoutError\",\n \"NetworkError\",\n \"PlaywrightError\",\n \"LlmError\",\n \"CloudinaryError\",\n \"FetchError\",\n \"RateLimitError\",\n \"RetryableError\",\n]);\n\nexport function isRetryable(error: unknown): boolean {\n if (!error || typeof error !== \"object\") return false;\n\n const err = error as Partial<{ name: string; message: string }>;\n\n if (err.name && RETRYABLE_ERROR_NAMES.has(err.name)) {\n return true;\n }\n\n if (typeof err.message === \"string\") {\n const lowerMessage = err.message.toLowerCase();\n if (\n lowerMessage.includes(\"timeout\") ||\n lowerMessage.includes(\"network\") ||\n lowerMessage.includes(\"refused\") ||\n lowerMessage.includes(\"reset\") ||\n lowerMessage.includes(\"rate limit\") ||\n lowerMessage.includes(\"too many requests\") ||\n lowerMessage.includes(\"unavailable\")\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nexport interface RetryOptions {\n maxAttempts?: number;\n delayMs?: number;\n multiplier?: number;\n onRetry?: (attempt: number, error: unknown, totalDelay: number) => void;\n}\n\nexport async function retry<T>(\n fn: () => Promise<T>,\n options?: RetryOptions\n): Promise<T> {\n const maxAttempts = options?.maxAttempts ?? 3;\n const initialDelay = options?.delayMs ?? 1000;\n const multiplier = options?.multiplier ?? 2;\n const onRetry = options?.onRetry;\n\n let lastError: unknown;\n let totalDelay = 0;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (!isRetryable(error) || attempt >= maxAttempts) {\n break;\n }\n\n const waitTime = initialDelay * Math.pow(multiplier, attempt - 1);\n totalDelay += waitTime;\n\n if (onRetry) {\n onRetry(attempt, error, totalDelay);\n }\n\n await new Promise((resolve) => setTimeout(resolve, waitTime));\n }\n }\n\n throw lastError;\n}\n","import { Page } from \"playwright\";\nimport getImageDetails, { CaptchaResult, LlmError } from \"./llm.js\";\nimport cloudinary from \"./cloudinary.js\";\nimport { retry } from \"./utils/retry.js\";\n\nexport interface SolveCaptchaOptions {\n /** Max attempts for image fetch / LLM call chain. Default 3. */\n maxRetries?: number;\n /** Initial delay in ms before first retry. Default 1000. */\n retryDelayMs?: number;\n /** Multiplier applied to retryDelayMs per attempt. Default 2. */\n retryMultiplier?: number;\n /** Timeout in ms for the image fetch + upload + LLM sequence. Default 60000. */\n timeoutMs?: number;\n}\n\nexport async function solveCaptchaFromPage(\n page: Page,\n captchaSelector: string = \"img.captcha-image\",\n outputScreenshotPath?: string,\n options?: SolveCaptchaOptions\n): Promise<CaptchaResult> {\n const maxRetries = options?.maxRetries ?? 3;\n const retryDelay = options?.retryDelayMs ?? 1000;\n const retryMultiplier = options?.retryMultiplier ?? 2;\n\n const captchaImage = page.locator(captchaSelector);\n\n try {\n await captchaImage.waitFor({ state: \"visible\" });\n } catch (error) {\n throw new Error(`CAPTCHA element not found or not visible. Selector: ${captchaSelector}`);\n }\n\n if (outputScreenshotPath) {\n await captchaImage.screenshot({ path: outputScreenshotPath });\n }\n\n // ---- Extract image as base64 (retries on network errors) ----------\n let imageData: { mimeType: string; base64: string };\n\n try {\n imageData = await retry(\n async () => {\n return await captchaImage.evaluate(async (img: HTMLImageElement) => {\n const response = await fetch(img.src);\n if (!response.ok) {\n throw new Error(`Image fetch failed: ${response.status}`);\n }\n const blob = await response.blob();\n return new Promise<{ mimeType: string; base64: string }>((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n const result = reader.result as string;\n resolve({\n mimeType: blob.type || \"image/png\",\n base64: result.split(\",\")[1],\n });\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n });\n },\n {\n maxAttempts: maxRetries,\n delayMs: retryDelay,\n multiplier: retryMultiplier,\n }\n );\n } catch (error) {\n throw new Error(`Failed to extract CAPTCHA image: ${error}`);\n }\n\n // ---- Upload to Cloudinary (retries on network errors) -------------\n let uploadResult;\n\n try {\n uploadResult = await retry(\n async () => {\n return await cloudinary.uploader.upload(\n `data:${imageData.mimeType};base64,${imageData.base64}`,\n {\n folder: \"playwright-captcha-images\",\n resource_type: \"image\",\n type: \"auth\",\n }\n );\n },\n {\n maxAttempts: maxRetries,\n delayMs: retryDelay,\n multiplier: retryMultiplier,\n }\n );\n } catch (error) {\n throw new Error(`Failed to upload CAPTCHA image to Cloudinary: ${error}`);\n }\n\n const imageUrl = uploadResult.secure_url;\n\n // ---- LLM solve (retries on timeout / rate-limit) ------------------\n try {\n const result = await retry(\n async () => {\n return await getImageDetails(imageUrl);\n },\n {\n maxAttempts: maxRetries,\n delayMs: retryDelay,\n multiplier: retryMultiplier,\n }\n );\n return result;\n } catch (error) {\n throw new Error(`Failed to solve CAPTCHA using AI: ${error}`);\n }\n}\n","import { chromium, Browser, Page, BrowserContext } from \"playwright\";\nimport { solveCaptchaFromPage } from \"./captchaSolve.js\";\nimport { retry, isRetryable } from \"./utils/retry.js\";\nimport { delay, randomBetween, executeWithRandomDelay } from \"./utils/humanDelay.js\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface LoginOptions {\n // Existing\n url: string;\n loginSelector?: string;\n passwordSelector?: string;\n captchaInputSelector?: string;\n submitButtonSelector?: string;\n headless?: boolean;\n outputScreenshotPath?: string;\n screenshotDelay?: number;\n\n // New – credentials override\n email?: string;\n password?: string;\n\n // New – retry\n retryCount?: number;\n baseRetryDelay?: number;\n\n // New – human-like timing\n delayRange?: { min: number; max: number };\n\n // New – proxy\n proxy?: { server: string; username?: string; password?: string };\n\n // New – session persistence\n cookieFile?: string;\n\n // New – video recording\n videoPath?: string;\n\n // New – webhook notifications\n webhookUrl?: string;\n}\n\nexport interface LoginResult {\n success: boolean;\n url: string;\n title: string;\n page: Page;\n error?: string;\n}\n\ninterface SerializedCookie {\n name: string;\n value: string;\n domain: string;\n path: string;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: \"Strict\" | \"Lax\" | \"None\";\n expires?: number;\n rawExpires?: string;\n}\n\nclass BrowserError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BrowserError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function validateEnv(options: LoginOptions): Promise<void> {\n const email = options.email || process.env.EMAIL || \"\";\n const password = options.password || process.env.PASSWORD || \"\";\n\n if (email === \"\" || email === \"dev@example.com\") {\n console.warn(\n `Warning: EMAIL is set to default or missing. ${\n options.email ? \"Override via LoginOptions.email.\" : 'Set EMAIL env var.'\n }`\n );\n }\n if (password === \"\" || password === \"22222222\") {\n console.warn(\n `Warning: PASSWORD is set to default or missing. ${\n options.password ? \"Override via LoginOptions.password.\" : 'Set PASSWORD env var.'\n }`\n );\n }\n}\n\n/** POST JSON payload to a webhook URL with a single internal retry. */\nexport async function sendWebhook(url: string, payload: unknown): Promise<boolean> {\n try {\n await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(10_000),\n });\n return true;\n } catch {\n try {\n await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(10_000),\n });\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/** Serialize Playwright browser-context cookies to a JSON file. */\nexport async function saveCookies(context: BrowserContext, filePath: string): Promise<void> {\n const cookies = await context.cookies();\n const dir = path.dirname(filePath);\n if (dir && !fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, JSON.stringify(cookies, null, 2), \"utf-8\");\n}\n\n/** Load cookies from a JSON file and add them to a browser context. */\nexport async function loadCookies(\n context: BrowserContext,\n filePath: string\n): Promise<void> {\n if (!fs.existsSync(filePath)) return;\n\n const data = fs.readFileSync(filePath, \"utf-8\");\n const cookies: SerializedCookie[] = JSON.parse(data);\n await context.addCookies(cookies as Parameters<BrowserContext[\"addCookies\"]>[0]);\n}\n\n/** Build Playwright-compatible proxy settings object. */\nexport function setupProxy(proxyConfig?: LoginOptions[\"proxy\"]) {\n if (!proxyConfig) return undefined;\n return {\n server: proxyConfig.server,\n username: proxyConfig.username,\n password: proxyConfig.password,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Core automation\n// ---------------------------------------------------------------------------\n\nexport async function automateLogin(options: LoginOptions): Promise<LoginResult> {\n let browser: Browser | null = null;\n let context: BrowserContext | null = null;\n let page: Page | null = null;\n\n try {\n await validateEnv(options);\n\n const retryCount = options.retryCount ?? 2;\n const baseRetryDelay = options.baseRetryDelay ?? 1000;\n const delayRange = options.delayRange ?? { min: 500, max: 1500 };\n\n // ---- Proxy --------------------------------------------------------\n const proxySettings = setupProxy(options.proxy);\n\n // ---- Launch browser -----------------------------------------------\n const launchOpts: Record<string, unknown> = {\n headless: options.headless ?? false,\n };\n if (proxySettings) {\n launchOpts.proxy = proxySettings;\n }\n\n browser = await chromium.launch(launchOpts);\n\n // ---- Context options ----------------------------------------------\n const contextOptions: Record<string, unknown> = {};\n if (proxySettings && !launchOpts.proxy) {\n contextOptions.proxy = proxySettings;\n }\n\n // Cookie persistence via storageState\n if (options.cookieFile) {\n contextOptions.storageState = options.cookieFile;\n }\n\n // Video recording via context recordVideo option\n if (options.videoPath) {\n const videoDir = path.isAbsolute(options.videoPath)\n ? path.dirname(options.videoPath)\n : path.join(process.cwd(), path.dirname(options.videoPath));\n contextOptions.recordVideo = {\n dir: videoDir,\n size: { width: 1280, height: 720 },\n };\n }\n\n context = await browser.newContext(contextOptions);\n page = await context.newPage();\n\n // ---- Selectors ----------------------------------------------------\n const loginSelector = options.loginSelector || \"#login\";\n const passwordSelector = options.passwordSelector || \"#password\";\n const captchaSelector = \"img.captcha-image\";\n const captchaInputSelector = options.captchaInputSelector || \"#captcha\";\n const submitButtonSelector = options.submitButtonSelector || \"button[type='submit']\";\n\n // ---- Single login attempt -----------------------------------------\n const attemptLogin = async (): Promise<{\n success: true;\n url: string;\n title: string;\n page: Page;\n }> => {\n await executeWithRandomDelay(\n async () =>\n page!.goto(options.url, { waitUntil: \"domcontentloaded\" }),\n ...Object.values(delayRange)\n );\n\n const emailVal = options.email || process.env.EMAIL || \"\";\n const passwordVal = options.password || process.env.PASSWORD || \"\";\n\n await executeWithRandomDelay(\n async () => page!.locator(loginSelector).fill(emailVal),\n ...Object.values(delayRange)\n );\n\n await executeWithRandomDelay(\n async () => page!.locator(passwordSelector).fill(passwordVal),\n ...Object.values(delayRange)\n );\n\n const captchaResult = await solveCaptchaFromPage(\n page!,\n captchaSelector,\n options.outputScreenshotPath ? undefined : undefined\n );\n\n await executeWithRandomDelay(\n async () => page!.locator(captchaInputSelector).fill(captchaResult.text),\n ...Object.values(delayRange)\n );\n\n await executeWithRandomDelay(\n async () => page!.locator(submitButtonSelector).click(),\n ...Object.values(delayRange)\n );\n\n try {\n await page!.waitForLoadState(\"domcontentloaded\", { timeout: 15000 });\n\n if (options.screenshotDelay) {\n await page!.waitForTimeout(options.screenshotDelay);\n }\n\n if (options.outputScreenshotPath) {\n await page!.screenshot({ path: options.outputScreenshotPath, fullPage: true });\n }\n } catch {\n console.warn(\"Warning: Could not wait for post-login state\");\n }\n\n return {\n success: true,\n url: page!.url(),\n title: await page!.title(),\n page: page!,\n };\n };\n\n // ---- Retry loop ---------------------------------------------------\n let result: Awaited<ReturnType<typeof attemptLogin>>;\n\n try {\n result = await retry(attemptLogin, {\n maxAttempts: retryCount + 1, // +1 for the initial attempt\n delayMs: baseRetryDelay,\n multiplier: 2,\n onRetry: (attempt, error, totalDelay) => {\n console.warn(\n `[retry] Attempt ${attempt} failed: ${error instanceof Error ? error.message : error}. Total backoff: ${totalDelay}ms`\n );\n },\n });\n } catch (error) {\n // ---- Failure path -----------------------------------------------\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n let pageTitle = \"\";\n if (page) {\n try {\n pageTitle = await page.title();\n } catch {\n pageTitle = \"failed to get title\";\n }\n }\n\n // Save cookies before leaving (partial session might still be useful)\n if (options.cookieFile && context) {\n try {\n await saveCookies(context, options.cookieFile);\n } catch {\n /* ignore */\n }\n }\n\n // Send failure webhook\n if (options.webhookUrl) {\n await sendWebhook(options.webhookUrl, {\n content: \"🔴 **Login Failed**\",\n embeds: [\n {\n title: \"Login Automation Failed\",\n fields: [\n { name: \"URL\", value: options.url, inline: true },\n {\n name: \"Error\",\n value:\n error instanceof Error ? error.message : String(error),\n inline: false,\n },\n ],\n color: 16734223,\n },\n ],\n });\n }\n\n return {\n success: false,\n url: page?.url() || \"\",\n title: pageTitle,\n page: page!,\n error: errorMessage,\n };\n }\n\n // ---- Success path -------------------------------------------------\n\n // Save cookies after successful login\n if (options.cookieFile && context) {\n try {\n await saveCookies(context, options.cookieFile);\n } catch (err) {\n console.warn(\"Warning: Could not save cookies:\", err);\n }\n }\n\n // Handle video recording — use the context-level recordVideo approach\n if (options.videoPath && page) {\n try {\n const videoObj = page.video();\n if (videoObj) {\n const outputPath = path.isAbsolute(options.videoPath!)\n ? options.videoPath!\n : path.join(process.cwd(), options.videoPath!);\n\n // Wait briefly then save\n await delay(500);\n await videoObj.saveAs(outputPath);\n console.log(`Video saved to ${outputPath}`);\n }\n } catch (err) {\n console.warn(\"Warning: Could not save video recording:\", err);\n }\n }\n\n // Send success webhook\n if (options.webhookUrl) {\n await sendWebhook(options.webhookUrl, {\n content: \"✅ **Login Successful**\",\n embeds: [\n {\n title: \"Login Automation Succeeded\",\n fields: [\n { name: \"URL\", value: options.url, inline: true },\n { name: \"Final URL\", value: result.url, inline: true },\n { name: \"Title\", value: result.title, inline: false },\n ],\n color: 5763719,\n },\n ],\n });\n }\n\n return {\n success: true,\n url: result.url,\n title: result.title,\n page: result.page,\n };\n } catch (error) {\n // Top-level catch for unexpected errors\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n let pageTitle = \"\";\n if (page) {\n try {\n pageTitle = await page.title();\n } catch {\n pageTitle = \"failed to get title\";\n }\n }\n\n // Save cookies on unexpected failure too\n if (options.cookieFile && context) {\n try {\n await saveCookies(context, options.cookieFile);\n } catch {\n /* ignore */\n }\n }\n\n // Send failure webhook\n if (options.webhookUrl) {\n await sendWebhook(options.webhookUrl, {\n content: \"🔴 **Login Error**\",\n embeds: [\n {\n title: \"Unexpected Login Error\",\n fields: [\n { name: \"URL\", value: options.url, inline: true },\n {\n name: \"Error\",\n value: error instanceof Error ? error.message : String(error),\n inline: false,\n },\n ],\n color: 16734223,\n },\n ],\n });\n }\n\n return {\n success: false,\n url: page?.url() || \"\",\n title: pageTitle,\n page: page!,\n error: errorMessage,\n };\n } finally {\n if (browser && !options.headless) {\n await browser.close().catch(() => {});\n }\n }\n}\n","/**\n * Returns a random integer between min and max (inclusive).\n */\nexport function randomBetween(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n/**\n * Delays execution for ms milliseconds with ±20% jitter.\n */\nexport function delay(ms: number): Promise<void> {\n const jittered = ms + Math.floor((Math.random() * 0.4 - 0.2) * ms);\n const actualDelay = Math.max(0, jittered);\n return new Promise((resolve) => setTimeout(resolve, actualDelay));\n}\n\n/**\n * Executes an async action and applies a random delay after it completes.\n * The delay is randomly chosen between minDelay and maxDelay (if provided),\n * defaulting to 500-1500ms.\n */\nexport async function executeWithRandomDelay<T>(\n action: () => Promise<T>,\n minDelay: number = 500,\n maxDelay: number = 1500\n): Promise<T> {\n const result = await action();\n const delayMs = randomBetween(minDelay, maxDelay);\n await delay(delayMs);\n return result;\n}\n"],"mappings":"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","names":["import_config","config","cloudinary"]}
|
package/dist/index.js
CHANGED
|
@@ -88,8 +88,62 @@ validateConfig(config);
|
|
|
88
88
|
cloudinary.config(config);
|
|
89
89
|
var cloudinary_default = cloudinary;
|
|
90
90
|
|
|
91
|
+
// src/utils/retry.ts
|
|
92
|
+
var RETRYABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
93
|
+
"TimeoutError",
|
|
94
|
+
"NetworkError",
|
|
95
|
+
"PlaywrightError",
|
|
96
|
+
"LlmError",
|
|
97
|
+
"CloudinaryError",
|
|
98
|
+
"FetchError",
|
|
99
|
+
"RateLimitError",
|
|
100
|
+
"RetryableError"
|
|
101
|
+
]);
|
|
102
|
+
function isRetryable(error) {
|
|
103
|
+
if (!error || typeof error !== "object") return false;
|
|
104
|
+
const err = error;
|
|
105
|
+
if (err.name && RETRYABLE_ERROR_NAMES.has(err.name)) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (typeof err.message === "string") {
|
|
109
|
+
const lowerMessage = err.message.toLowerCase();
|
|
110
|
+
if (lowerMessage.includes("timeout") || lowerMessage.includes("network") || lowerMessage.includes("refused") || lowerMessage.includes("reset") || lowerMessage.includes("rate limit") || lowerMessage.includes("too many requests") || lowerMessage.includes("unavailable")) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
async function retry(fn, options) {
|
|
117
|
+
const maxAttempts = options?.maxAttempts ?? 3;
|
|
118
|
+
const initialDelay = options?.delayMs ?? 1e3;
|
|
119
|
+
const multiplier = options?.multiplier ?? 2;
|
|
120
|
+
const onRetry = options?.onRetry;
|
|
121
|
+
let lastError;
|
|
122
|
+
let totalDelay = 0;
|
|
123
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
124
|
+
try {
|
|
125
|
+
return await fn();
|
|
126
|
+
} catch (error) {
|
|
127
|
+
lastError = error;
|
|
128
|
+
if (!isRetryable(error) || attempt >= maxAttempts) {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
const waitTime = initialDelay * Math.pow(multiplier, attempt - 1);
|
|
132
|
+
totalDelay += waitTime;
|
|
133
|
+
if (onRetry) {
|
|
134
|
+
onRetry(attempt, error, totalDelay);
|
|
135
|
+
}
|
|
136
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
throw lastError;
|
|
140
|
+
}
|
|
141
|
+
|
|
91
142
|
// src/captchaSolve.ts
|
|
92
|
-
async function solveCaptchaFromPage(page, captchaSelector = "img.captcha-image", outputScreenshotPath) {
|
|
143
|
+
async function solveCaptchaFromPage(page, captchaSelector = "img.captcha-image", outputScreenshotPath, options) {
|
|
144
|
+
const maxRetries = options?.maxRetries ?? 3;
|
|
145
|
+
const retryDelay = options?.retryDelayMs ?? 1e3;
|
|
146
|
+
const retryMultiplier = options?.retryMultiplier ?? 2;
|
|
93
147
|
const captchaImage = page.locator(captchaSelector);
|
|
94
148
|
try {
|
|
95
149
|
await captchaImage.waitFor({ state: "visible" });
|
|
@@ -101,37 +155,54 @@ async function solveCaptchaFromPage(page, captchaSelector = "img.captcha-image",
|
|
|
101
155
|
}
|
|
102
156
|
let imageData;
|
|
103
157
|
try {
|
|
104
|
-
imageData = await
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
158
|
+
imageData = await retry(
|
|
159
|
+
async () => {
|
|
160
|
+
return await captchaImage.evaluate(async (img) => {
|
|
161
|
+
const response = await fetch(img.src);
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
throw new Error(`Image fetch failed: ${response.status}`);
|
|
164
|
+
}
|
|
165
|
+
const blob = await response.blob();
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
const reader = new FileReader();
|
|
168
|
+
reader.onload = () => {
|
|
169
|
+
const result = reader.result;
|
|
170
|
+
resolve({
|
|
171
|
+
mimeType: blob.type || "image/png",
|
|
172
|
+
base64: result.split(",")[1]
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
reader.onerror = reject;
|
|
176
|
+
reader.readAsDataURL(blob);
|
|
118
177
|
});
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
maxAttempts: maxRetries,
|
|
182
|
+
delayMs: retryDelay,
|
|
183
|
+
multiplier: retryMultiplier
|
|
184
|
+
}
|
|
185
|
+
);
|
|
124
186
|
} catch (error) {
|
|
125
187
|
throw new Error(`Failed to extract CAPTCHA image: ${error}`);
|
|
126
188
|
}
|
|
127
189
|
let uploadResult;
|
|
128
190
|
try {
|
|
129
|
-
uploadResult = await
|
|
130
|
-
|
|
191
|
+
uploadResult = await retry(
|
|
192
|
+
async () => {
|
|
193
|
+
return await cloudinary_default.uploader.upload(
|
|
194
|
+
`data:${imageData.mimeType};base64,${imageData.base64}`,
|
|
195
|
+
{
|
|
196
|
+
folder: "playwright-captcha-images",
|
|
197
|
+
resource_type: "image",
|
|
198
|
+
type: "auth"
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
},
|
|
131
202
|
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
203
|
+
maxAttempts: maxRetries,
|
|
204
|
+
delayMs: retryDelay,
|
|
205
|
+
multiplier: retryMultiplier
|
|
135
206
|
}
|
|
136
207
|
);
|
|
137
208
|
} catch (error) {
|
|
@@ -139,7 +210,16 @@ async function solveCaptchaFromPage(page, captchaSelector = "img.captcha-image",
|
|
|
139
210
|
}
|
|
140
211
|
const imageUrl = uploadResult.secure_url;
|
|
141
212
|
try {
|
|
142
|
-
const result = await
|
|
213
|
+
const result = await retry(
|
|
214
|
+
async () => {
|
|
215
|
+
return await llm_default(imageUrl);
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
maxAttempts: maxRetries,
|
|
219
|
+
delayMs: retryDelay,
|
|
220
|
+
multiplier: retryMultiplier
|
|
221
|
+
}
|
|
222
|
+
);
|
|
143
223
|
return result;
|
|
144
224
|
} catch (error) {
|
|
145
225
|
throw new Error(`Failed to solve CAPTCHA using AI: ${error}`);
|
|
@@ -148,62 +228,266 @@ async function solveCaptchaFromPage(page, captchaSelector = "img.captcha-image",
|
|
|
148
228
|
|
|
149
229
|
// src/login.ts
|
|
150
230
|
import { chromium } from "playwright";
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
231
|
+
|
|
232
|
+
// src/utils/humanDelay.ts
|
|
233
|
+
function randomBetween(min, max) {
|
|
234
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
235
|
+
}
|
|
236
|
+
function delay(ms) {
|
|
237
|
+
const jittered = ms + Math.floor((Math.random() * 0.4 - 0.2) * ms);
|
|
238
|
+
const actualDelay = Math.max(0, jittered);
|
|
239
|
+
return new Promise((resolve) => setTimeout(resolve, actualDelay));
|
|
240
|
+
}
|
|
241
|
+
async function executeWithRandomDelay(action, minDelay = 500, maxDelay = 1500) {
|
|
242
|
+
const result = await action();
|
|
243
|
+
const delayMs = randomBetween(minDelay, maxDelay);
|
|
244
|
+
await delay(delayMs);
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/login.ts
|
|
249
|
+
import * as fs from "fs";
|
|
250
|
+
import * as path from "path";
|
|
251
|
+
async function validateEnv(options) {
|
|
252
|
+
const email = options.email || process.env.EMAIL || "";
|
|
253
|
+
const password = options.password || process.env.PASSWORD || "";
|
|
254
|
+
if (email === "" || email === "dev@example.com") {
|
|
255
|
+
console.warn(
|
|
256
|
+
`Warning: EMAIL is set to default or missing. ${options.email ? "Override via LoginOptions.email." : "Set EMAIL env var."}`
|
|
257
|
+
);
|
|
155
258
|
}
|
|
156
|
-
if (
|
|
157
|
-
|
|
259
|
+
if (password === "" || password === "22222222") {
|
|
260
|
+
console.warn(
|
|
261
|
+
`Warning: PASSWORD is set to default or missing. ${options.password ? "Override via LoginOptions.password." : "Set PASSWORD env var."}`
|
|
262
|
+
);
|
|
158
263
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
264
|
+
}
|
|
265
|
+
async function sendWebhook(url, payload) {
|
|
266
|
+
try {
|
|
267
|
+
await fetch(url, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: { "Content-Type": "application/json" },
|
|
270
|
+
body: JSON.stringify(payload),
|
|
271
|
+
signal: AbortSignal.timeout(1e4)
|
|
272
|
+
});
|
|
273
|
+
return true;
|
|
274
|
+
} catch {
|
|
275
|
+
try {
|
|
276
|
+
await fetch(url, {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: { "Content-Type": "application/json" },
|
|
279
|
+
body: JSON.stringify(payload),
|
|
280
|
+
signal: AbortSignal.timeout(1e4)
|
|
281
|
+
});
|
|
282
|
+
return true;
|
|
283
|
+
} catch {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
163
286
|
}
|
|
164
287
|
}
|
|
288
|
+
async function saveCookies(context, filePath) {
|
|
289
|
+
const cookies = await context.cookies();
|
|
290
|
+
const dir = path.dirname(filePath);
|
|
291
|
+
if (dir && !fs.existsSync(dir)) {
|
|
292
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
293
|
+
}
|
|
294
|
+
fs.writeFileSync(filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
295
|
+
}
|
|
296
|
+
async function loadCookies(context, filePath) {
|
|
297
|
+
if (!fs.existsSync(filePath)) return;
|
|
298
|
+
const data = fs.readFileSync(filePath, "utf-8");
|
|
299
|
+
const cookies = JSON.parse(data);
|
|
300
|
+
await context.addCookies(cookies);
|
|
301
|
+
}
|
|
302
|
+
function setupProxy(proxyConfig) {
|
|
303
|
+
if (!proxyConfig) return void 0;
|
|
304
|
+
return {
|
|
305
|
+
server: proxyConfig.server,
|
|
306
|
+
username: proxyConfig.username,
|
|
307
|
+
password: proxyConfig.password
|
|
308
|
+
};
|
|
309
|
+
}
|
|
165
310
|
async function automateLogin(options) {
|
|
166
311
|
let browser = null;
|
|
167
312
|
let context = null;
|
|
168
313
|
let page = null;
|
|
169
314
|
try {
|
|
170
|
-
await validateEnv();
|
|
171
|
-
|
|
315
|
+
await validateEnv(options);
|
|
316
|
+
const retryCount = options.retryCount ?? 2;
|
|
317
|
+
const baseRetryDelay = options.baseRetryDelay ?? 1e3;
|
|
318
|
+
const delayRange = options.delayRange ?? { min: 500, max: 1500 };
|
|
319
|
+
const proxySettings = setupProxy(options.proxy);
|
|
320
|
+
const launchOpts = {
|
|
172
321
|
headless: options.headless ?? false
|
|
173
|
-
}
|
|
174
|
-
|
|
322
|
+
};
|
|
323
|
+
if (proxySettings) {
|
|
324
|
+
launchOpts.proxy = proxySettings;
|
|
325
|
+
}
|
|
326
|
+
browser = await chromium.launch(launchOpts);
|
|
327
|
+
const contextOptions = {};
|
|
328
|
+
if (proxySettings && !launchOpts.proxy) {
|
|
329
|
+
contextOptions.proxy = proxySettings;
|
|
330
|
+
}
|
|
331
|
+
if (options.cookieFile) {
|
|
332
|
+
contextOptions.storageState = options.cookieFile;
|
|
333
|
+
}
|
|
334
|
+
if (options.videoPath) {
|
|
335
|
+
const videoDir = path.isAbsolute(options.videoPath) ? path.dirname(options.videoPath) : path.join(process.cwd(), path.dirname(options.videoPath));
|
|
336
|
+
contextOptions.recordVideo = {
|
|
337
|
+
dir: videoDir,
|
|
338
|
+
size: { width: 1280, height: 720 }
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
context = await browser.newContext(contextOptions);
|
|
175
342
|
page = await context.newPage();
|
|
176
|
-
await page.goto(options.url, { waitUntil: "domcontentloaded" });
|
|
177
343
|
const loginSelector = options.loginSelector || "#login";
|
|
178
344
|
const passwordSelector = options.passwordSelector || "#password";
|
|
179
345
|
const captchaSelector = "img.captcha-image";
|
|
180
346
|
const captchaInputSelector = options.captchaInputSelector || "#captcha";
|
|
181
347
|
const submitButtonSelector = options.submitButtonSelector || "button[type='submit']";
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
options.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
348
|
+
const attemptLogin = async () => {
|
|
349
|
+
await executeWithRandomDelay(
|
|
350
|
+
async () => page.goto(options.url, { waitUntil: "domcontentloaded" }),
|
|
351
|
+
...Object.values(delayRange)
|
|
352
|
+
);
|
|
353
|
+
const emailVal = options.email || process.env.EMAIL || "";
|
|
354
|
+
const passwordVal = options.password || process.env.PASSWORD || "";
|
|
355
|
+
await executeWithRandomDelay(
|
|
356
|
+
async () => page.locator(loginSelector).fill(emailVal),
|
|
357
|
+
...Object.values(delayRange)
|
|
358
|
+
);
|
|
359
|
+
await executeWithRandomDelay(
|
|
360
|
+
async () => page.locator(passwordSelector).fill(passwordVal),
|
|
361
|
+
...Object.values(delayRange)
|
|
362
|
+
);
|
|
363
|
+
const captchaResult = await solveCaptchaFromPage(
|
|
364
|
+
page,
|
|
365
|
+
captchaSelector,
|
|
366
|
+
options.outputScreenshotPath ? void 0 : void 0
|
|
367
|
+
);
|
|
368
|
+
await executeWithRandomDelay(
|
|
369
|
+
async () => page.locator(captchaInputSelector).fill(captchaResult.text),
|
|
370
|
+
...Object.values(delayRange)
|
|
371
|
+
);
|
|
372
|
+
await executeWithRandomDelay(
|
|
373
|
+
async () => page.locator(submitButtonSelector).click(),
|
|
374
|
+
...Object.values(delayRange)
|
|
375
|
+
);
|
|
376
|
+
try {
|
|
377
|
+
await page.waitForLoadState("domcontentloaded", { timeout: 15e3 });
|
|
378
|
+
if (options.screenshotDelay) {
|
|
379
|
+
await page.waitForTimeout(options.screenshotDelay);
|
|
380
|
+
}
|
|
381
|
+
if (options.outputScreenshotPath) {
|
|
382
|
+
await page.screenshot({ path: options.outputScreenshotPath, fullPage: true });
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
console.warn("Warning: Could not wait for post-login state");
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
success: true,
|
|
389
|
+
url: page.url(),
|
|
390
|
+
title: await page.title(),
|
|
391
|
+
page
|
|
392
|
+
};
|
|
393
|
+
};
|
|
394
|
+
let result;
|
|
191
395
|
try {
|
|
192
|
-
await
|
|
193
|
-
|
|
194
|
-
|
|
396
|
+
result = await retry(attemptLogin, {
|
|
397
|
+
maxAttempts: retryCount + 1,
|
|
398
|
+
// +1 for the initial attempt
|
|
399
|
+
delayMs: baseRetryDelay,
|
|
400
|
+
multiplier: 2,
|
|
401
|
+
onRetry: (attempt, error, totalDelay) => {
|
|
402
|
+
console.warn(
|
|
403
|
+
`[retry] Attempt ${attempt} failed: ${error instanceof Error ? error.message : error}. Total backoff: ${totalDelay}ms`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
} catch (error) {
|
|
408
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
409
|
+
let pageTitle = "";
|
|
410
|
+
if (page) {
|
|
411
|
+
try {
|
|
412
|
+
pageTitle = await page.title();
|
|
413
|
+
} catch {
|
|
414
|
+
pageTitle = "failed to get title";
|
|
415
|
+
}
|
|
195
416
|
}
|
|
196
|
-
if (options.
|
|
197
|
-
|
|
417
|
+
if (options.cookieFile && context) {
|
|
418
|
+
try {
|
|
419
|
+
await saveCookies(context, options.cookieFile);
|
|
420
|
+
} catch {
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (options.webhookUrl) {
|
|
424
|
+
await sendWebhook(options.webhookUrl, {
|
|
425
|
+
content: "\u{1F534} **Login Failed**",
|
|
426
|
+
embeds: [
|
|
427
|
+
{
|
|
428
|
+
title: "Login Automation Failed",
|
|
429
|
+
fields: [
|
|
430
|
+
{ name: "URL", value: options.url, inline: true },
|
|
431
|
+
{
|
|
432
|
+
name: "Error",
|
|
433
|
+
value: error instanceof Error ? error.message : String(error),
|
|
434
|
+
inline: false
|
|
435
|
+
}
|
|
436
|
+
],
|
|
437
|
+
color: 16734223
|
|
438
|
+
}
|
|
439
|
+
]
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
success: false,
|
|
444
|
+
url: page?.url() || "",
|
|
445
|
+
title: pageTitle,
|
|
446
|
+
page,
|
|
447
|
+
error: errorMessage
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
if (options.cookieFile && context) {
|
|
451
|
+
try {
|
|
452
|
+
await saveCookies(context, options.cookieFile);
|
|
453
|
+
} catch (err) {
|
|
454
|
+
console.warn("Warning: Could not save cookies:", err);
|
|
198
455
|
}
|
|
199
|
-
}
|
|
200
|
-
|
|
456
|
+
}
|
|
457
|
+
if (options.videoPath && page) {
|
|
458
|
+
try {
|
|
459
|
+
const videoObj = page.video();
|
|
460
|
+
if (videoObj) {
|
|
461
|
+
const outputPath = path.isAbsolute(options.videoPath) ? options.videoPath : path.join(process.cwd(), options.videoPath);
|
|
462
|
+
await delay(500);
|
|
463
|
+
await videoObj.saveAs(outputPath);
|
|
464
|
+
console.log(`Video saved to ${outputPath}`);
|
|
465
|
+
}
|
|
466
|
+
} catch (err) {
|
|
467
|
+
console.warn("Warning: Could not save video recording:", err);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (options.webhookUrl) {
|
|
471
|
+
await sendWebhook(options.webhookUrl, {
|
|
472
|
+
content: "\u2705 **Login Successful**",
|
|
473
|
+
embeds: [
|
|
474
|
+
{
|
|
475
|
+
title: "Login Automation Succeeded",
|
|
476
|
+
fields: [
|
|
477
|
+
{ name: "URL", value: options.url, inline: true },
|
|
478
|
+
{ name: "Final URL", value: result.url, inline: true },
|
|
479
|
+
{ name: "Title", value: result.title, inline: false }
|
|
480
|
+
],
|
|
481
|
+
color: 5763719
|
|
482
|
+
}
|
|
483
|
+
]
|
|
484
|
+
});
|
|
201
485
|
}
|
|
202
486
|
return {
|
|
203
487
|
success: true,
|
|
204
|
-
url:
|
|
205
|
-
title:
|
|
206
|
-
page
|
|
488
|
+
url: result.url,
|
|
489
|
+
title: result.title,
|
|
490
|
+
page: result.page
|
|
207
491
|
};
|
|
208
492
|
} catch (error) {
|
|
209
493
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -215,6 +499,31 @@ async function automateLogin(options) {
|
|
|
215
499
|
pageTitle = "failed to get title";
|
|
216
500
|
}
|
|
217
501
|
}
|
|
502
|
+
if (options.cookieFile && context) {
|
|
503
|
+
try {
|
|
504
|
+
await saveCookies(context, options.cookieFile);
|
|
505
|
+
} catch {
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (options.webhookUrl) {
|
|
509
|
+
await sendWebhook(options.webhookUrl, {
|
|
510
|
+
content: "\u{1F534} **Login Error**",
|
|
511
|
+
embeds: [
|
|
512
|
+
{
|
|
513
|
+
title: "Unexpected Login Error",
|
|
514
|
+
fields: [
|
|
515
|
+
{ name: "URL", value: options.url, inline: true },
|
|
516
|
+
{
|
|
517
|
+
name: "Error",
|
|
518
|
+
value: error instanceof Error ? error.message : String(error),
|
|
519
|
+
inline: false
|
|
520
|
+
}
|
|
521
|
+
],
|
|
522
|
+
color: 16734223
|
|
523
|
+
}
|
|
524
|
+
]
|
|
525
|
+
});
|
|
526
|
+
}
|
|
218
527
|
return {
|
|
219
528
|
success: false,
|
|
220
529
|
url: page?.url() || "",
|
|
@@ -233,11 +542,29 @@ async function automateLogin(options) {
|
|
|
233
542
|
// src/index.ts
|
|
234
543
|
var index_default = {
|
|
235
544
|
solveCaptchaFromPage,
|
|
236
|
-
automateLogin
|
|
545
|
+
automateLogin,
|
|
546
|
+
sendWebhook,
|
|
547
|
+
saveCookies,
|
|
548
|
+
loadCookies,
|
|
549
|
+
setupProxy,
|
|
550
|
+
randomBetween,
|
|
551
|
+
delay,
|
|
552
|
+
executeWithRandomDelay,
|
|
553
|
+
retry,
|
|
554
|
+
isRetryable
|
|
237
555
|
};
|
|
238
556
|
export {
|
|
239
557
|
automateLogin,
|
|
240
558
|
index_default as default,
|
|
559
|
+
delay,
|
|
560
|
+
executeWithRandomDelay,
|
|
561
|
+
isRetryable,
|
|
562
|
+
loadCookies,
|
|
563
|
+
randomBetween,
|
|
564
|
+
retry,
|
|
565
|
+
saveCookies,
|
|
566
|
+
sendWebhook,
|
|
567
|
+
setupProxy,
|
|
241
568
|
solveCaptchaFromPage
|
|
242
569
|
};
|
|
243
570
|
//# sourceMappingURL=index.js.map
|