import { sleep } from '@fuman/utils' import { z } from 'zod' import { ffetch } from './fetch.ts' import { getEnv } from './misc.ts' const CreateTaskResponse = z.object({ errorId: z.number(), errorCode: z.string().optional().nullable(), taskId: z.number(), }) const GetTaskResultResponse = z.object({ errorId: z.number(), errorCode: z.string().optional().nullable(), status: z.enum(['ready', 'processing']), solution: z.unknown().optional(), }) export async function solveCaptcha(task: unknown) { const res = await ffetch.post('https://api.capmonster.cloud/createTask', { json: { clientKey: getEnv('CAPMONSTER_API_TOKEN'), task, }, }).parsedJson(CreateTaskResponse) if (res.errorId) { throw new Error(`createTask error ${res.errorId}: ${res.errorCode}`) } const taskId = res.taskId await sleep(5_000) let requestCount = 0 while (true) { requestCount += 1 if (requestCount > 100) { // "Limit: 120 requests per task. If the limit is exceeded, the user's account may be temporarily locked." // just to be safe throw new Error('captcha request count exceeded') } const res = await ffetch.post('https://api.capmonster.cloud/getTaskResult', { json: { clientKey: getEnv('CAPMONSTER_API_TOKEN'), taskId, }, }).parsedJson(GetTaskResultResponse) if (res.errorId) { throw new Error(`getTaskResult error ${res.errorId}: ${res.errorCode}`) } if (res.status === 'ready') { return res.solution } await sleep(2_000) } } export async function solveRecaptcha(params?: { url: string siteKey: string s?: string userAgent?: string cookies?: string isInvisible?: boolean }) { const res = await solveCaptcha({ type: 'RecaptchaV2TaskProxyless', websiteURL: params?.url, websiteKey: params?.siteKey, recaptchaDataSValue: params?.s, userAgent: params?.userAgent, cookies: params?.cookies, isInvisible: params?.isInvisible, }) if (typeof res !== 'object' || !res || !('gRecaptchaResponse' in res) || typeof res.gRecaptchaResponse !== 'string') { throw new Error('invalid recaptcha response') } return res.gRecaptchaResponse }