Skip to content

Commit

Permalink
feat(recaptcha_plugin):supporting capmonster 'https://docs.capmonster…
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyass bzitar authored and ilyass bzitar committed Jul 17, 2024
1 parent 39248f1 commit d6cedcd
Show file tree
Hide file tree
Showing 2 changed files with 318 additions and 0 deletions.
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 packages/puppeteer-extra-plugin-recaptcha/src/provider/capmonster.ts
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
}

0 comments on commit d6cedcd

Please sign in to comment.