-
-
Notifications
You must be signed in to change notification settings - Fork 742
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(recaptcha_plugin):supporting capmonster 'https://docs.capmonster…
- Loading branch information
ilyass bzitar
authored and
ilyass bzitar
committed
Jul 17, 2024
1 parent
39248f1
commit d6cedcd
Showing
2 changed files
with
318 additions
and
0 deletions.
There are no files selected for viewing
185 changes: 185 additions & 0 deletions
185
packages/puppeteer-extra-plugin-recaptcha/src/provider/capMonster-api.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
// TODO: Create our own API wrapper | ||
|
||
var https = require('https') | ||
var url = require('url') | ||
|
||
var apiKey | ||
var apiUrl = 'https://api.capmonster.cloud' | ||
var apiInUrl = 'http://api.capmonster.cloud/in.php' | ||
//var apigetTaskUrl = 'https://api.capmonster.cloud/getTaskResult' | ||
// var apiMethod = 'base64' | ||
// var SOFT_ID = '2589' | ||
|
||
var defaultOptions = { | ||
pollingInterval: 40000, | ||
retries: 4, | ||
} | ||
|
||
function pollCaptcha(captchaId, options, invalid, callback) { | ||
invalid = invalid.bind({ options: options, captchaId: captchaId }) | ||
var intervalId = setInterval(function () { | ||
// var httpsRequestOptions = url.parse( | ||
// apiResUrl + | ||
// '?action=get&soft_id=' + | ||
// SOFT_ID + | ||
// '&key=' + | ||
// apiKey + | ||
// '&id=' + | ||
// captchaId | ||
// ) | ||
|
||
var httpsRequestOptions = { | ||
method: 'POST', | ||
hostname: apiUrl, | ||
path: '/getTaskResult', | ||
headers: { | ||
clientKey: apiKey, | ||
taskId: captchaId, | ||
nocache: 1, | ||
}, | ||
} | ||
var request = https.request(httpsRequestOptions, function (response) { | ||
var body = '' | ||
|
||
response.on('data', function (chunk) { | ||
body += chunk | ||
}) | ||
|
||
response.on('end', function () { | ||
const res = JSON.parse(body) | ||
if (res.status === 'processing') { | ||
return | ||
} | ||
|
||
clearInterval(intervalId) | ||
|
||
// var result = body // .split('|') | ||
if (res.status !== 'ready') { | ||
callback(res) // error | ||
} else { | ||
callback( | ||
null, | ||
{ | ||
id: captchaId, | ||
text: res.solution.gRecaptchaResponse, | ||
}, | ||
invalid | ||
) | ||
} | ||
callback = function () {} // prevent the callback from being called more than once, if multiple https requests are open at the same time. | ||
}) | ||
}) | ||
request.on('error', function (e) { | ||
request.destroy() | ||
callback(e) | ||
}) | ||
request.end() | ||
}, options.pollingInterval || defaultOptions.pollingInterval) | ||
} | ||
|
||
export const setApiKey = function (key) { | ||
apiKey = key | ||
} | ||
|
||
export const decodeReCaptcha = function ( | ||
captchaMethod, | ||
captcha, | ||
pageUrl, | ||
extraData, | ||
options, | ||
callback | ||
) { | ||
if (!callback) { | ||
callback = options | ||
options = defaultOptions | ||
} | ||
var httpsRequestOptions = url.parse(apiInUrl) | ||
httpsRequestOptions.method = 'POST' | ||
|
||
var postData = { | ||
key: apiKey, | ||
method: captchaMethod, | ||
pageURL: pageUrl, | ||
...extraData, | ||
} | ||
if (captchaMethod === 'userrecaptcha') { | ||
postData.googlekey = captcha | ||
} | ||
if (captchaMethod === 'hcaptcha') { | ||
postData.sitekey = captcha | ||
} | ||
postData.nocache = 1 | ||
|
||
var request = https.request(httpsRequestOptions, function (response) { | ||
var body = '' | ||
|
||
response.on('data', function (chunk) { | ||
body += chunk | ||
}) | ||
|
||
response.on('end', function () { | ||
var result = JSON.parse(body) | ||
if (result.errorId !== 0) { | ||
return callback(result.errorCode) | ||
} | ||
|
||
pollCaptcha( | ||
result.taskId, | ||
options, | ||
function (error) { | ||
var callbackToInitialCallback = callback | ||
|
||
report(this.captchaId) | ||
|
||
if (error) { | ||
return callbackToInitialCallback('CAPTCHA_FAILED') | ||
} | ||
|
||
if (!this.options.retries) { | ||
this.options.retries = defaultOptions.retries | ||
} | ||
if (this.options.retries > 1) { | ||
this.options.retries = this.options.retries - 1 | ||
decodeReCaptcha( | ||
captchaMethod, | ||
captcha, | ||
pageUrl, | ||
extraData, | ||
this.options, | ||
callback | ||
) | ||
} else { | ||
callbackToInitialCallback('CAPTCHA_FAILED_TOO_MANY_TIMES') | ||
} | ||
}, | ||
callback | ||
) | ||
}) | ||
}) | ||
request.on('error', function (e) { | ||
request.destroy() | ||
callback(e) | ||
}) | ||
request.write(postData) | ||
request.end() | ||
} | ||
|
||
export const report = function (captchaId) { | ||
var reportUrl = | ||
apiInUrl + | ||
'?action=reportbad&soft_id=' + | ||
'&key=' + | ||
apiKey + | ||
'&id=' + | ||
captchaId | ||
var options = url.parse(reportUrl) | ||
|
||
var request = https.request(options, function (response) { | ||
// var body = '' | ||
// response.on('data', function(chunk) { | ||
// body += chunk | ||
// }) | ||
// response.on('end', function() {}) | ||
}) | ||
request.end() | ||
} |
133 changes: 133 additions & 0 deletions
133
packages/puppeteer-extra-plugin-recaptcha/src/provider/capmonster.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
export const PROVIDER_ID = 'capmonster' | ||
|
||
import * as types from '../types' | ||
|
||
export interface CapmonsterProviderOpts { | ||
url: string | ||
token: string | ||
pollingInterval?: number | ||
} | ||
|
||
import Debug from 'debug' | ||
const debug = Debug(`puppeteer-extra-plugin:recaptcha:${PROVIDER_ID}`) | ||
|
||
import * as solver from './capMonster-api' | ||
|
||
const secondsBetweenDates = (before: Date, after: Date) => | ||
(after.getTime() - before.getTime()) / 1000 | ||
|
||
export interface DecodeRecaptchaAsyncResult { | ||
err?: any | ||
result?: any | ||
invalid?: any | ||
} | ||
|
||
export interface TwoCaptchaProviderOpts { | ||
useEnterpriseFlag?: boolean | ||
useActionValue?: boolean | ||
} | ||
|
||
const providerOptsDefaults: TwoCaptchaProviderOpts = { | ||
useEnterpriseFlag: false, // Seems to make solving chance worse? | ||
useActionValue: true, | ||
} | ||
|
||
async function decodeRecaptchaAsync( | ||
token: string, | ||
vendor: types.CaptchaVendor, | ||
sitekey: string, | ||
url: string, | ||
extraData: any, | ||
opts = { pollingInterval: 2000 } | ||
): Promise<DecodeRecaptchaAsyncResult> { | ||
return new Promise((resolve) => { | ||
const cb = (err: any, result: any, invalid: any) => | ||
resolve({ err, result, invalid }) | ||
try { | ||
solver.setApiKey(token) | ||
|
||
let method = 'userrecaptcha' | ||
if (vendor === 'hcaptcha') { | ||
method = 'hcaptcha' | ||
} | ||
solver.decodeReCaptcha(method, sitekey, url, extraData, opts, cb) | ||
} catch (error) { | ||
return resolve({ err: error }) | ||
} | ||
}) | ||
} | ||
|
||
export async function getSolutions( | ||
captchas: types.CaptchaInfo[] = [], | ||
token: string = '', | ||
opts: TwoCaptchaProviderOpts = {} | ||
): Promise<types.GetSolutionsResult> { | ||
opts = { ...providerOptsDefaults, ...opts } | ||
const solutions = await Promise.all( | ||
captchas.map((c) => getSolution(c, token, opts)) | ||
) | ||
return { solutions, error: solutions.find((s) => !!s.error) } | ||
} | ||
|
||
async function getSolution( | ||
captcha: types.CaptchaInfo, | ||
token: string, | ||
opts: TwoCaptchaProviderOpts | ||
): Promise<types.CaptchaSolution> { | ||
const solution: types.CaptchaSolution = { | ||
_vendor: captcha._vendor, | ||
provider: PROVIDER_ID, | ||
} | ||
try { | ||
if (!captcha || !captcha.sitekey || !captcha.url || !captcha.id) { | ||
throw new Error('Missing data in captcha') | ||
} | ||
solution.id = captcha.id | ||
solution.requestAt = new Date() | ||
debug('Requesting solution..', solution) | ||
const extraData = {} | ||
if (captcha.s) { | ||
extraData['data-s'] = captcha.s // google site specific property | ||
} | ||
if (opts.useActionValue && captcha.action) { | ||
extraData['action'] = captcha.action // Optional v3/enterprise action | ||
} | ||
if (opts.useEnterpriseFlag && captcha.isEnterprise) { | ||
extraData['enterprise'] = 1 | ||
} | ||
|
||
if ( | ||
process.env['CAPMONSTER_PROXY_TYPE'] && | ||
process.env['CAPMONSTER_PROXY_ADDRESS'] | ||
) { | ||
extraData['proxytype'] = | ||
process.env['CAPMONSTER_PROXY_TYPE'].toUpperCase() | ||
extraData['proxy'] = process.env['CAPMONSTER_PROXY_ADDRESS'] | ||
} | ||
|
||
const { err, result, invalid } = await decodeRecaptchaAsync( | ||
token, | ||
captcha._vendor, | ||
captcha.sitekey, | ||
captcha.url, | ||
extraData | ||
) | ||
debug('Got response', { err, result, invalid }) | ||
if (err) throw new Error(`${PROVIDER_ID} error: ${err}`) | ||
if (!result || !result.text || !result.id) { | ||
throw new Error(`${PROVIDER_ID} error: Missing response data: ${result}`) | ||
} | ||
solution.providerCaptchaId = result.id | ||
solution.text = result.text | ||
solution.responseAt = new Date() | ||
solution.hasSolution = !!solution.text | ||
solution.duration = secondsBetweenDates( | ||
solution.requestAt, | ||
solution.responseAt | ||
) | ||
} catch (error) { | ||
debug('Error', error) | ||
solution.error = error.toString() | ||
} | ||
return solution | ||
} |