添加whatshub工单
This commit is contained in:
Executable
+153
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 付费 Captcha API 兜底:CapSolver / 2Captcha 统一 Turnstile 求解接口
|
||||
*/
|
||||
const {
|
||||
CAPTCHA_PROVIDER,
|
||||
CAPTCHA_API_KEY,
|
||||
CAPTCHA_MAX_WAIT_MS,
|
||||
} = require('./constants');
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {Record<string, string>} [headers]
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
async function httpJsonPost(url, body, headers = {}) {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...headers },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const text = await res.text();
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (_) {
|
||||
throw new Error(`Captcha API 返回非 JSON: ${text.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
async function httpJsonGet(url) {
|
||||
const res = await fetch(url);
|
||||
const text = await res.text();
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (_) {
|
||||
throw new Error(`Captcha API 返回非 JSON: ${text.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CapSolver Turnstile 求解
|
||||
* @param {{ pageUrl: string, sitekey: string, apiKey: string }} params
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function solveViaCapSolver({ pageUrl, sitekey, apiKey }) {
|
||||
const create = await httpJsonPost('https://api.capsolver.com/createTask', {
|
||||
clientKey: apiKey,
|
||||
task: {
|
||||
type: 'AntiTurnstileTaskProxyLess',
|
||||
websiteURL: pageUrl,
|
||||
websiteKey: sitekey,
|
||||
},
|
||||
});
|
||||
|
||||
if (create.errorId !== 0 || !create.taskId) {
|
||||
throw new Error(`CapSolver createTask 失败: ${create.errorDescription || create.errorCode || 'unknown'}`);
|
||||
}
|
||||
|
||||
const deadline = Date.now() + CAPTCHA_MAX_WAIT_MS;
|
||||
while (Date.now() < deadline) {
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
const result = await httpJsonPost('https://api.capsolver.com/getTaskResult', {
|
||||
clientKey: apiKey,
|
||||
taskId: create.taskId,
|
||||
});
|
||||
|
||||
if (result.status === 'ready' && result.solution && result.solution.token) {
|
||||
return result.solution.token;
|
||||
}
|
||||
if (result.errorId !== 0) {
|
||||
throw new Error(`CapSolver getTaskResult 失败: ${result.errorDescription || result.errorCode}`);
|
||||
}
|
||||
}
|
||||
throw new Error('CapSolver 求解超时');
|
||||
}
|
||||
|
||||
/**
|
||||
* 2Captcha Turnstile 求解
|
||||
* @param {{ pageUrl: string, sitekey: string, apiKey: string }} params
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function solveVia2Captcha({ pageUrl, sitekey, apiKey }) {
|
||||
const params = new URLSearchParams({
|
||||
key: apiKey,
|
||||
method: 'turnstile',
|
||||
sitekey,
|
||||
pageurl: pageUrl,
|
||||
json: '1',
|
||||
});
|
||||
|
||||
const create = await httpJsonGet(`https://2captcha.com/in.php?${params.toString()}`);
|
||||
if (create.status !== 1 || !create.request) {
|
||||
throw new Error(`2Captcha in.php 失败: ${create.request || create.error_text || 'unknown'}`);
|
||||
}
|
||||
|
||||
const taskId = create.request;
|
||||
const deadline = Date.now() + CAPTCHA_MAX_WAIT_MS;
|
||||
while (Date.now() < deadline) {
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
const result = await httpJsonGet(
|
||||
`https://2captcha.com/res.php?key=${encodeURIComponent(apiKey)}&action=get&id=${encodeURIComponent(taskId)}&json=1`
|
||||
);
|
||||
if (result.status === 1 && result.request) {
|
||||
return result.request;
|
||||
}
|
||||
if (result.request && result.request !== 'CAPCHA_NOT_READY') {
|
||||
throw new Error(`2Captcha res.php 失败: ${result.request}`);
|
||||
}
|
||||
}
|
||||
throw new Error('2Captcha 求解超时');
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一 Turnstile 求解入口
|
||||
* @param {{ pageUrl: string, sitekey: string, provider?: string, apiKey?: string }} params
|
||||
* @returns {Promise<{ token: string, provider: string }>}
|
||||
*/
|
||||
async function solveTurnstile({ pageUrl, sitekey, provider, apiKey }) {
|
||||
const resolvedProvider = (provider || CAPTCHA_PROVIDER).toLowerCase();
|
||||
const resolvedKey = apiKey || CAPTCHA_API_KEY;
|
||||
|
||||
if (!sitekey) {
|
||||
throw new Error('无法提取 Turnstile sitekey');
|
||||
}
|
||||
if (!resolvedKey) {
|
||||
throw new Error('未配置 CAPTCHA_API_KEY,无法使用付费 Captcha 兜底');
|
||||
}
|
||||
|
||||
let token;
|
||||
if (resolvedProvider === '2captcha') {
|
||||
token = await solveVia2Captcha({ pageUrl, sitekey, apiKey: resolvedKey });
|
||||
} else {
|
||||
token = await solveViaCapSolver({ pageUrl, sitekey, apiKey: resolvedKey });
|
||||
}
|
||||
|
||||
return { token, provider: resolvedProvider };
|
||||
}
|
||||
|
||||
/**
|
||||
* Captcha API 是否已配置
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isCaptchaConfigured() {
|
||||
return CAPTCHA_API_KEY.length > 0;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
solveTurnstile,
|
||||
isCaptchaConfigured,
|
||||
};
|
||||
Reference in New Issue
Block a user