NAME commited on
Commit
63c5b6b
·
1 Parent(s): 6f8cb92
Files changed (6) hide show
  1. Dockerfile +43 -0
  2. README.md +3 -4
  3. package.json +23 -0
  4. postinstall.js +122 -0
  5. server.js +201 -0
  6. solver.js +377 -0
Dockerfile ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-bullseye
2
+
3
+ RUN apt-get update && apt-get install -y \
4
+ ffmpeg \
5
+ chromium \
6
+ chromium-driver \
7
+ fonts-liberation \
8
+ libasound2 \
9
+ libatk-bridge2.0-0 \
10
+ libatk1.0-0 \
11
+ libatspi2.0-0 \
12
+ libcups2 \
13
+ libdbus-1-3 \
14
+ libdrm2 \
15
+ libgbm1 \
16
+ libgtk-3-0 \
17
+ libnspr4 \
18
+ libnss3 \
19
+ libwayland-client0 \
20
+ libxcomposite1 \
21
+ libxdamage1 \
22
+ libxfixes3 \
23
+ libxkbcommon0 \
24
+ libxrandr2 \
25
+ xdg-utils \
26
+ && rm -rf /var/lib/apt/lists/*
27
+
28
+ WORKDIR /app
29
+
30
+ COPY package*.json ./
31
+ COPY postinstall.js ./
32
+
33
+
34
+ RUN npm install
35
+
36
+ COPY . .
37
+
38
+ EXPOSE 7860
39
+
40
+ ENV PLAYWRIGHT_BROWSERS_PATH=/usr/bin/chromium
41
+ ENV NODE_ENV=production
42
+
43
+ CMD ["node", "server.js"]
README.md CHANGED
@@ -1,11 +1,10 @@
1
  ---
2
  title: V2
3
- emoji: 🦀
4
- colorFrom: indigo
5
- colorTo: green
6
  sdk: docker
7
  pinned: false
8
- short_description: 🙏
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: V2
3
+ emoji: 📉
4
+ colorFrom: red
5
+ colorTo: blue
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
package.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "recaptcha-solver-api",
3
+ "version": "1.0.0",
4
+ "description": "reCAPTCHA Solver API for Hugging Face",
5
+ "main": "server.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "start": "node server.js",
9
+ "dev": "node server.js",
10
+ "postinstall": "node postinstall.js"
11
+ },
12
+ "dependencies": {
13
+ "express": "^4.18.2",
14
+ "playwright-core": "^1.40.0",
15
+ "cors": "^2.8.5",
16
+ "vosk-koffi": "^1.0.8",
17
+ "wav": "^1.0.2",
18
+ "yauzl": "^2.10.0"
19
+ },
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ }
23
+ }
postinstall.js ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import https from 'https';
5
+ import yauzl from 'yauzl';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ const URL = "https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip";
12
+ const MODEL_DIR = path.resolve(__dirname, "model");
13
+
14
+ async function main() {
15
+ if (!fs.existsSync(MODEL_DIR)) {
16
+ fs.mkdirSync(MODEL_DIR, { recursive: true });
17
+ }
18
+
19
+ if (fs.existsSync(path.resolve(MODEL_DIR, "DONE"))) {
20
+ console.log("Model already downloaded");
21
+ return;
22
+ }
23
+
24
+ const zip = path.resolve(os.tmpdir(), path.basename(URL));
25
+ await download(URL, zip);
26
+ console.log("Downloaded model to", zip);
27
+
28
+ await unzip(zip, MODEL_DIR);
29
+ fs.unlinkSync(zip);
30
+ }
31
+
32
+ function download(url, to, redirect = 0) {
33
+ if (redirect === 0) {
34
+ console.log(`Downloading ${url} to ${to}`);
35
+ } else {
36
+ console.log(`Redirecting to ${url}`);
37
+ }
38
+
39
+ return new Promise((resolve, reject) => {
40
+ if (!fs.existsSync(path.dirname(to))) {
41
+ fs.mkdirSync(path.dirname(to), { recursive: true });
42
+ }
43
+
44
+ let done = true;
45
+ const file = fs.createWriteStream(to);
46
+ const request = https.get(url, (res) => {
47
+ if (res.statusCode === 302 && res.headers.location !== undefined) {
48
+ done = false;
49
+ file.close();
50
+ resolve(download(res.headers.location, to, redirect + 1));
51
+ return;
52
+ }
53
+ res.pipe(file);
54
+ });
55
+
56
+ file.on("finish", () => {
57
+ if (done) {
58
+ resolve(to);
59
+ }
60
+ });
61
+
62
+ request.on("error", (err) => {
63
+ fs.unlink(to, () => reject(err));
64
+ });
65
+
66
+ file.on("error", (err) => {
67
+ fs.unlink(to, () => reject(err));
68
+ });
69
+
70
+ request.end();
71
+ });
72
+ }
73
+
74
+ function unzip(zip, dest) {
75
+ const dir = path.basename(zip, ".zip");
76
+ return new Promise((resolve, reject) => {
77
+ yauzl.open(zip, { lazyEntries: true }, (err, zipfile) => {
78
+ if (err) {
79
+ reject(err);
80
+ }
81
+ zipfile.readEntry();
82
+ zipfile
83
+ .on("entry", (entry) => {
84
+ if (/\/$/.test(entry.fileName)) {
85
+ zipfile.readEntry();
86
+ } else {
87
+ zipfile.openReadStream(entry, (err, stream) => {
88
+ if (err) {
89
+ reject(err);
90
+ }
91
+ const f = path.resolve(dest, entry.fileName.replace(`${dir}/`, ""));
92
+ if (!fs.existsSync(path.dirname(f))) {
93
+ fs.mkdirSync(path.dirname(f), { recursive: true });
94
+ console.log("Created directory", path.dirname(f));
95
+ }
96
+ stream.pipe(fs.createWriteStream(f));
97
+ stream
98
+ .on("end", () => {
99
+ console.log("Extracted", f);
100
+ zipfile.readEntry();
101
+ })
102
+ .on("error", (err) => {
103
+ reject(err);
104
+ });
105
+ });
106
+ }
107
+ })
108
+ .on("error", (err) => {
109
+ reject(err);
110
+ })
111
+ .on("end", () => {
112
+ console.log("Extracted all files");
113
+ fs.writeFileSync(path.resolve(dest, "DONE"), "");
114
+ })
115
+ .on("close", () => {
116
+ resolve();
117
+ });
118
+ });
119
+ });
120
+ }
121
+
122
+ main().catch(console.error);
server.js ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import { chromium } from 'playwright-core';
3
+ import cors from 'cors';
4
+ import { solve } from './solver.js';
5
+
6
+ const app = express();
7
+ const PORT = process.env.PORT || 7860;
8
+
9
+ app.use(cors());
10
+ app.use(express.json());
11
+
12
+ app.get('/', (req, res) => {
13
+ res.json({
14
+ status: 'ok',
15
+ message: 'reCAPTCHA Solver API is running',
16
+ endpoints: {
17
+ solve: '/api/solve?url=YOUR_URL',
18
+ health: '/health'
19
+ }
20
+ });
21
+ });
22
+
23
+ app.get('/health', (req, res) => {
24
+ res.json({ status: 'healthy' });
25
+ });
26
+
27
+ app.get('/api/solve', async (req, res) => {
28
+ const { url } = req.query;
29
+
30
+ if (!url) {
31
+ return res.status(400).json({
32
+ error: 'Missing required parameter',
33
+ message: 'Please provide a URL parameter'
34
+ });
35
+ }
36
+
37
+ let browser;
38
+ const startTime = Date.now();
39
+
40
+ try {
41
+ console.log(`[API] Starting to solve reCAPTCHA for: ${url}`);
42
+
43
+ browser = await chromium.launch({
44
+ headless: true,
45
+ args: [
46
+ '--no-sandbox',
47
+ '--disable-setuid-sandbox',
48
+ '--disable-dev-shm-usage',
49
+ '--disable-gpu',
50
+ '--disable-software-rasterizer',
51
+ '--disable-extensions',
52
+ '--disable-blink-features=AutomationControlled'
53
+ ],
54
+ executablePath: process.env.PLAYWRIGHT_BROWSERS_PATH || '/usr/bin/chromium'
55
+ });
56
+
57
+ const context = await browser.newContext({
58
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
59
+ viewport: { width: 1280, height: 720 }
60
+ });
61
+
62
+ const page = await context.newPage();
63
+
64
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
65
+
66
+ console.log('[API] Attempting to solve reCAPTCHA...');
67
+
68
+ await solve(page);
69
+
70
+ const solveTime = ((Date.now() - startTime) / 1000).toFixed(2);
71
+ console.log(`[API] reCAPTCHA solved in ${solveTime}s`);
72
+
73
+ const pageUrl = page.url();
74
+ const pageTitle = await page.title();
75
+
76
+ const token = await page.evaluate(() => {
77
+ const textarea = document.querySelector('[name="g-recaptcha-response"]');
78
+ return textarea ? textarea.value : null;
79
+ });
80
+
81
+ await browser.close();
82
+
83
+ res.json({
84
+ success: true,
85
+ message: 'reCAPTCHA solved successfully',
86
+ solveTime: `${solveTime}s`,
87
+ url: pageUrl,
88
+ title: pageTitle,
89
+ hasToken: !!token,
90
+ tokenLength: token ? token.length : 0,
91
+ timestamp: new Date().toISOString()
92
+ });
93
+
94
+ } catch (error) {
95
+ console.error('[API] Error solving reCAPTCHA:', error);
96
+
97
+ if (browser) {
98
+ await browser.close().catch(err => console.error('Error closing browser:', err));
99
+ }
100
+
101
+ res.status(500).json({
102
+ success: false,
103
+ error: 'Failed to solve reCAPTCHA',
104
+ message: error.message,
105
+ timestamp: new Date().toISOString()
106
+ });
107
+ }
108
+ });
109
+
110
+ app.post('/api/solve', async (req, res) => {
111
+ const { url, submitSelector } = req.body;
112
+
113
+ if (!url) {
114
+ return res.status(400).json({
115
+ error: 'Missing required field',
116
+ message: 'Please provide a URL in the request body'
117
+ });
118
+ }
119
+
120
+ let browser;
121
+ const startTime = Date.now();
122
+
123
+ try {
124
+ console.log(`[API] Starting to solve reCAPTCHA for: ${url}`);
125
+
126
+ browser = await chromium.launch({
127
+ headless: true,
128
+ args: [
129
+ '--no-sandbox',
130
+ '--disable-setuid-sandbox',
131
+ '--disable-dev-shm-usage',
132
+ '--disable-gpu',
133
+ '--disable-blink-features=AutomationControlled'
134
+ ],
135
+ executablePath: process.env.PLAYWRIGHT_BROWSERS_PATH || '/usr/bin/chromium'
136
+ });
137
+
138
+ const context = await browser.newContext({
139
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
140
+ viewport: { width: 1280, height: 720 }
141
+ });
142
+
143
+ const page = await context.newPage();
144
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
145
+
146
+ console.log('[API] Attempting to solve reCAPTCHA...');
147
+ await solve(page);
148
+
149
+ await page.waitForTimeout(2000);
150
+
151
+ if (submitSelector) {
152
+ await page.click(submitSelector);
153
+ await page.waitForTimeout(3000);
154
+ }
155
+
156
+ const solveTime = ((Date.now() - startTime) / 1000).toFixed(2);
157
+ console.log(`[API] reCAPTCHA solved in ${solveTime}s`);
158
+
159
+ const pageUrl = page.url();
160
+
161
+ await browser.close();
162
+
163
+ res.json({
164
+ success: true,
165
+ message: 'reCAPTCHA solved successfully',
166
+ solveTime: `${solveTime}s`,
167
+ url: pageUrl,
168
+ timestamp: new Date().toISOString()
169
+ });
170
+
171
+ } catch (error) {
172
+ console.error('[API] Error solving reCAPTCHA:', error);
173
+
174
+ if (browser) {
175
+ await browser.close().catch(err => console.error('Error closing browser:', err));
176
+ }
177
+
178
+ res.status(500).json({
179
+ success: false,
180
+ error: 'Failed to solve reCAPTCHA',
181
+ message: error.message,
182
+ timestamp: new Date().toISOString()
183
+ });
184
+ }
185
+ });
186
+
187
+ app.listen(PORT, '0.0.0.0', () => {
188
+ console.log(`🚀 reCAPTCHA Solver API running on port ${PORT}`);
189
+ console.log(`📍 Health check: http://localhost:${PORT}/health`);
190
+ console.log(`🔧 Solve endpoint: http://localhost:${PORT}/api/solve?url=YOUR_URL`);
191
+ });
192
+
193
+ process.on('SIGTERM', () => {
194
+ console.log('SIGTERM received, shutting down gracefully...');
195
+ process.exit(0);
196
+ });
197
+
198
+ process.on('SIGINT', () => {
199
+ console.log('SIGINT received, shutting down gracefully...');
200
+ process.exit(0);
201
+ });
solver.js ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { spawnSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { Readable } from 'stream';
6
+ import vosk from 'vosk-koffi';
7
+ import wav from 'wav';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ vosk.setLogLevel(-1);
14
+ const MODEL_DIR = path.resolve(__dirname, "model");
15
+ const model = new vosk.Model(MODEL_DIR);
16
+
17
+ const SOURCE_FILE = "sound.mp3";
18
+ const OUT_FILE = "out.wav";
19
+ const MAIN_FRAME = "iframe[title='reCAPTCHA']";
20
+ const BFRAME = "iframe[src*='google.com/recaptcha'][src*='bframe']";
21
+ const CHALLENGE = "body > div > div";
22
+
23
+ class Mutex {
24
+ constructor(name = "Mutex") {
25
+ this.name = name;
26
+ this._locked = false;
27
+ this._queue = [];
28
+ }
29
+
30
+ async lock(tag) {
31
+ if (this._locked) {
32
+ if (tag) {
33
+ console.log(`[${this.name}] ${tag} waiting`);
34
+ }
35
+
36
+ return new Promise((resolve) => {
37
+ this._queue.push(() => {
38
+ this._lock(tag);
39
+ resolve();
40
+ });
41
+ });
42
+ }
43
+
44
+ this._lock(tag);
45
+ }
46
+
47
+ unlock(tag) {
48
+ if (this._locked) {
49
+ if (tag) {
50
+ console.log(`[${this.name}] ${tag} unlocked`);
51
+ }
52
+
53
+ this._locked = false;
54
+ this._queue.shift()?.();
55
+ }
56
+ }
57
+
58
+ _lock(tag) {
59
+ this._locked = true;
60
+
61
+ if (tag) {
62
+ console.log(`[${this.name}] ${tag} locked`);
63
+ }
64
+ }
65
+ }
66
+
67
+ function sleep(ms) {
68
+ return new Promise((resolve) => setTimeout(resolve, ms));
69
+ }
70
+
71
+ function createDir() {
72
+ const dir = path.resolve(os.tmpdir(), "reSOLVER-" + Math.random().toString().slice(2));
73
+ if (fs.existsSync(dir)) {
74
+ fs.rmSync(dir, { recursive: true });
75
+ }
76
+ fs.mkdirSync(dir, { recursive: true });
77
+ return dir;
78
+ }
79
+
80
+ function convert(dir, ffmpeg = "ffmpeg") {
81
+ const args = [
82
+ "-loglevel",
83
+ "error",
84
+ "-i",
85
+ SOURCE_FILE,
86
+ "-acodec",
87
+ "pcm_s16le",
88
+ "-ac",
89
+ "1",
90
+ "-ar",
91
+ "16000",
92
+ OUT_FILE,
93
+ ];
94
+
95
+ spawnSync(ffmpeg, args, { cwd: dir });
96
+ }
97
+
98
+ function recognize(dir) {
99
+ return new Promise((resolve) => {
100
+ const stream = fs.createReadStream(path.resolve(dir, OUT_FILE), { highWaterMark: 4096 });
101
+
102
+ const reader = new wav.Reader();
103
+ const readable = new Readable().wrap(reader);
104
+ reader.on("format", async ({ audioFormat, sampleRate, channels }) => {
105
+ if (audioFormat != 1 || channels != 1) {
106
+ throw new Error("Audio file must be WAV with mono PCM.");
107
+ }
108
+
109
+ const rec = new vosk.Recognizer({ model, sampleRate });
110
+ rec.setMaxAlternatives(10);
111
+ rec.setWords(true);
112
+ rec.setPartialWords(true);
113
+
114
+ for await (const data of readable) {
115
+ const end_of_speech = rec.acceptWaveform(data);
116
+ if (end_of_speech) {
117
+ const result = rec
118
+ .result()
119
+ .alternatives.sort((a, b) => b.confidence - a.confidence)[0].text;
120
+ stream.close(() => resolve(result));
121
+ }
122
+ }
123
+
124
+ rec.free();
125
+ });
126
+
127
+ stream.pipe(reader);
128
+ });
129
+ }
130
+
131
+ async function getText(res, ffmpeg = "ffmpeg") {
132
+ const tempDir = createDir();
133
+
134
+ fs.writeFileSync(path.resolve(tempDir, SOURCE_FILE), await res.body());
135
+ convert(tempDir, ffmpeg);
136
+ const result = await recognize(tempDir);
137
+
138
+ fs.rmSync(tempDir, { recursive: true });
139
+ return result;
140
+ }
141
+
142
+ export async function solve(page, { delay = 64, wait = 5000, retry = 3, ffmpeg = "ffmpeg" } = {}) {
143
+ console.log("Starting reCAPTCHA solver...");
144
+
145
+ try {
146
+ await page.waitForSelector('iframe', { state: "attached", timeout: wait });
147
+ console.log("Found iframes on page");
148
+
149
+ const allIframes = await page.$$('iframe');
150
+ console.log(`Total iframes found: ${allIframes.length}`);
151
+
152
+ for (let i = 0; i < allIframes.length; i++) {
153
+ const src = await allIframes[i].getAttribute('src');
154
+ const title = await allIframes[i].getAttribute('title');
155
+ console.log(`Iframe ${i}: src=${src}, title=${title}`);
156
+ }
157
+ } catch (error) {
158
+ console.error("Error finding iframes:", error.message);
159
+ throw new Error("No reCAPTCHA detected");
160
+ }
161
+
162
+ let invisible = false;
163
+
164
+ let b_iframe = null;
165
+ const bframeSelectors = [
166
+ "iframe[src*='/recaptcha/api2/bframe']",
167
+ "iframe[src*='/recaptcha/enterprise/bframe']",
168
+ "iframe[src*='bframe']"
169
+ ];
170
+
171
+ for (const selector of bframeSelectors) {
172
+ try {
173
+ await page.waitForSelector(selector, { state: "attached", timeout: 2000 });
174
+ b_iframe = await page.$(selector);
175
+ if (b_iframe) {
176
+ console.log(`Found bframe with selector: ${selector}`);
177
+ break;
178
+ }
179
+ } catch {
180
+ continue;
181
+ }
182
+ }
183
+
184
+ if (b_iframe === null) {
185
+ console.log("Bframe not found yet, checking main frame...");
186
+
187
+ try {
188
+ await page.waitForSelector(MAIN_FRAME, { state: "attached", timeout: wait });
189
+ const iframe = await page.$(MAIN_FRAME);
190
+
191
+ if (iframe === null) {
192
+ throw new Error("Could not find reCAPTCHA iframe");
193
+ }
194
+
195
+ const box_page = await iframe.contentFrame();
196
+ if (box_page === null) {
197
+ throw new Error("Could not find reCAPTCHA iframe content");
198
+ }
199
+
200
+ invisible = (await box_page.$("div.rc-anchor-invisible")) ? true : false;
201
+ console.log("invisible:", invisible);
202
+
203
+ if (invisible === true) {
204
+ return false;
205
+ } else {
206
+ const label = await box_page.$("#recaptcha-anchor-label");
207
+ if (label === null) {
208
+ throw new Error("Could not find reCAPTCHA label");
209
+ }
210
+
211
+ console.log("Clicking reCAPTCHA checkbox...");
212
+ await label.click();
213
+
214
+ await sleep(2000);
215
+
216
+ for (const selector of bframeSelectors) {
217
+ b_iframe = await page.$(selector);
218
+ if (b_iframe) {
219
+ console.log(`Found bframe after click: ${selector}`);
220
+ break;
221
+ }
222
+ }
223
+ }
224
+ } catch (error) {
225
+ console.error("Error handling main frame:", error.message);
226
+ throw new Error("Could not find reCAPTCHA popup iframe");
227
+ }
228
+ }
229
+
230
+ if (b_iframe === null) {
231
+ throw new Error("Could not find reCAPTCHA popup iframe after all attempts");
232
+ }
233
+
234
+ const bframe = await b_iframe.contentFrame();
235
+ if (bframe === null) {
236
+ throw new Error("Could not find reCAPTCHA popup iframe content");
237
+ }
238
+
239
+ await sleep(1000);
240
+
241
+ const challenge = await bframe.$(CHALLENGE);
242
+ if (challenge === null) {
243
+ console.log("No challenge found yet, this might be OK");
244
+ return false;
245
+ }
246
+
247
+ const required = await challenge.evaluate(
248
+ (elm) => !elm.classList.contains("rc-footer"),
249
+ );
250
+ console.log("action required:", required);
251
+
252
+ if (required === false) {
253
+ return false;
254
+ }
255
+
256
+ await bframe.waitForSelector("#recaptcha-audio-button", { timeout: wait });
257
+ const audio_button = await bframe.$("#recaptcha-audio-button");
258
+ if (audio_button === null) {
259
+ throw new Error("Could not find reCAPTCHA audio button");
260
+ }
261
+
262
+ const mutex = new Mutex();
263
+ await mutex.lock("init");
264
+ let passed = false;
265
+ let answer = Promise.resolve("");
266
+ const listener = async (res) => {
267
+ if (res.headers()["content-type"] === "audio/mp3") {
268
+ console.log(`got audio from ${res.url()}`);
269
+ answer = new Promise((resolve) => {
270
+ getText(res, ffmpeg)
271
+ .then(resolve)
272
+ .catch(() => undefined);
273
+ });
274
+ mutex.unlock("get sound");
275
+ } else if (
276
+ res.url().startsWith("https://www.google.com/recaptcha/api2/userverify") ||
277
+ res.url().startsWith("https://www.google.com/recaptcha/enterprise/userverify")
278
+ ) {
279
+ const raw = (await res.body()).toString().replace(")]}'\n", "");
280
+ const json = JSON.parse(raw);
281
+ passed = json[2] === 1;
282
+ mutex.unlock("verified");
283
+ }
284
+ };
285
+ page.on("response", listener);
286
+
287
+ await audio_button.click();
288
+
289
+ let tried = 0;
290
+ while (passed === false) {
291
+ if (tried++ >= retry) {
292
+ throw new Error("Could not solve reCAPTCHA");
293
+ }
294
+
295
+ await Promise.race([
296
+ mutex.lock("ready"),
297
+ sleep(wait).then(() => {
298
+ throw new Error("No Audio Found");
299
+ }),
300
+ ]);
301
+ await bframe.waitForSelector("#audio-source", { state: "attached", timeout: wait });
302
+ await bframe.waitForSelector("#audio-response", { timeout: wait });
303
+
304
+ console.log("recognized:", await answer);
305
+
306
+ const input = await bframe.$("#audio-response");
307
+ if (input === null) {
308
+ throw new Error("Could not find reCAPTCHA audio input");
309
+ }
310
+
311
+ await input.type(await answer, { delay });
312
+
313
+ const button = await bframe.$("#recaptcha-verify-button");
314
+ if (button === null) {
315
+ throw new Error("Could not find reCAPTCHA verify button");
316
+ }
317
+
318
+ await button.click();
319
+ await mutex.lock("done");
320
+ console.log("passed:", passed);
321
+ }
322
+
323
+ page.off("response", listener);
324
+
325
+ await sleep(1000);
326
+
327
+ let token = null;
328
+
329
+ try {
330
+ const iframe = await page.$(MAIN_FRAME);
331
+ if (iframe) {
332
+ const box_page = await iframe.contentFrame();
333
+ if (box_page) {
334
+ const textarea = await box_page.$('#g-recaptcha-response');
335
+ if (textarea) {
336
+ token = await textarea.evaluate(el => el.value);
337
+ console.log("Token found in textarea:", token?.substring(0, 50) + "...");
338
+ }
339
+ }
340
+ }
341
+ } catch (error) {
342
+ console.log("Error extracting token from iframe:", error.message);
343
+ }
344
+
345
+ if (!token) {
346
+ try {
347
+ const textarea = await page.$('#g-recaptcha-response');
348
+ if (textarea) {
349
+ token = await textarea.evaluate(el => el.value);
350
+ console.log("Token found in main page:", token?.substring(0, 50) + "...");
351
+ }
352
+ } catch (error) {
353
+ console.log("Error extracting token from main page:", error.message);
354
+ }
355
+ }
356
+
357
+ if (!token) {
358
+ try {
359
+ const allTextareas = await page.$('textarea');
360
+ for (const textarea of allTextareas) {
361
+ const name = await textarea.getAttribute('name');
362
+ const id = await textarea.getAttribute('id');
363
+ if (name === 'g-recaptcha-response' || id === 'g-recaptcha-response') {
364
+ token = await textarea.evaluate(el => el.value);
365
+ console.log("Token found in textarea by attribute:", token?.substring(0, 50) + "...");
366
+ break;
367
+ }
368
+ }
369
+ } catch (error) {
370
+ console.log("Error searching all textareas:", error.message);
371
+ }
372
+ }
373
+
374
+ console.log("Final token:", token ? `${token.substring(0, 50)}... (length: ${token.length})` : "not found");
375
+
376
+ return { success: true, token };
377
+ }