Gemini bussiness 自动刷新逻辑,接入 yescapture 打码服务,做到了基本 100% 的成功率

【回顾】 在之前的自动化注册或者刷新中会出现验证码发送错误的情况,原因在于获取的谷歌验证分数太低导致发送接口错误

【探索】通过抓包发现,以下接口就是发送邮件的接口

https://accountverification.business.gemini.google/_/IdentityPlatformFrontendUI/data/batchexecute?rpcids=IjXaFf&source-path=%2Fv1%2Fverify-oob-code&bl=boq_cloud-identity-identityplatform-frontend_20251210.10_p1&hl=en&rt=c

请求体为
f.req:[[["IjXaFf",,null,"generic"]]]

里面的 0cAFcWe 就是开头的验证

因此,只需要接入打码服务即可完成再次发送,测试结果为成功率基本 100%

完整 nodejs 代码如下:

 const fs = require('fs');
const os = require('os');
const path = require('path');
const axios = require('axios');
const express = require('express');
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const { ImapFlow } = require('imapflow');
const { simpleParser } = require('mailparser');

puppeteer.use(StealthPlugin());

const app = express();
const port = 3000;

// =============================== // 配置项 // =============================== const WEBSITE_KEY = '6Ld8dCcrAAAAAFVbDMVZy8aNRwCjakBVaDEdRUH8';
const WEBSITE_URL = 'https://accountverification.business.gemini.google';

const sleep = (ms) => new Promise(r => setTimeout(r, ms));

function createTempUserDataDir() {
  return fs.mkdtempSync(path.join(os.tmpdir(), 'pptr-profile-'));
}

/**
* 邮件获取类
*/
class GeminiFetcher { constructor(email, clientId, refreshToken) { this.email = email; this.clientId = clientId; this.refreshToken = refreshToken; } async getAccessToken() { const params = new URLSearchParams({ client_id: this.clientId, grant_type: 'refresh_token', refresh_token: this.refreshToken }); const res = await axios.post('https://login.microsoftonline.com/consumers/oauth2/v2.0/token', params.toString()); return res.data.access_token; } async tryFetchOnce() { const token = await this.getAccessToken(); const client = new ImapFlow({ host: 'outlook.office365.com', port: 993, secure: true, auth: { user: this.email, accessToken: token }, logger: false }); await client.connect(); let code = null; try { for (const mailbox of ['INBOX', 'Junk']) { let lock; try { lock = await client.getMailboxLock(mailbox); const messages = await client.search({ all: true }); const lastUids = messages.slice(-5).reverse(); const tenMinutesAgo = Date.now() - 10 * 60 * 1000; for (let uid of lastUids) { let msg = await client.fetchOne(uid, { source: true }); let parsed = await simpleParser(msg.source); if (parsed.date.getTime() > tenMinutesAgo && parsed.subject && (parsed.subject.includes('Gemini') || parsed.subject.includes('Google'))) { const content = parsed.text + (parsed.html || ""); const match = content.match(/[A-Z0-9]{6}/); if (match) { code = match[0]; break; } } } } catch (e) { } finally { if (lock) lock.release(); } if (code) break; } } finally { await client.logout(); } return code; } } async function startPollingForCode(email, clientId, refreshToken) { const fetcher = new GeminiFetcher(email, clientId, refreshToken); for (let i = 1; i <= 10; i++) { try { console.log(`[${email}] 尝试获取邮件验证码 (${i}/10)...`); const code = await fetcher.tryFetchOnce(); if (code) { console.log(`[${email}] 成功获取到验证码: ${code}`); return code; } } catch (err) { console.error(`[${email}] 获取验证码报错: ${err.message}`); } await sleep(4000); } return null; } async function getCaptchaToken(apiKey) { try { console.log('🤖 正在向 YesCaptcha 请求人机识别 Token...'); const createResp = await axios.post('https://api.yescaptcha.com/createTask', { clientKey: apiKey, task: { websiteURL: WEBSITE_URL, websiteKey: WEBSITE_KEY, pageAction: 'verify_oob_code', type: 'RecaptchaV3TaskProxylessM1' } }); const taskId = createResp.data.taskId; for (let i = 0; i < 20; i++) { await sleep(3000); const resultResp = await axios.post('https://api.yescaptcha.com/getTaskResult', { clientKey: apiKey, taskId: taskId }); if (resultResp.data.status === 'ready') { console.log('🤖 YesCaptcha Token 获取成功'); return resultResp.data.solution.gRecaptchaResponse; } } } catch (error) { console.error(`❌ YesCaptcha 错误: ${error.message}`); return null; } } function patchPayload(rawBody, newToken) { const params = new URLSearchParams(rawBody); let fReq = params.get('f.req'); if (!fReq) return rawBody; const tokenRegex = /0[3c]AFc[a-zA-Z0-9_\-]{50,}/g; if (tokenRegex.test(fReq)) { params.set('f.req', fReq.replace(tokenRegex, newToken)); } return params.toString(); } // =============================== // 核心任务逻辑 // =============================== async function runLoginTask(accountData, apiKey) { const parts = accountData.split('----'); const [email, password, clientId, refreshToken] = parts; const userDataDir = createTempUserDataDir(); console.log(`🚀 [任务开始] 账号: ${email}`); let resolveCodeSent; const codeSentBarrier = new Promise((resolve) => { resolveCodeSent = resolve; }); let resolveAuthBarrier; const authBarrier = new Promise((resolve) => { resolveAuthBarrier = resolve; }); console.log('🌐 正在启动浏览器...'); const browser = await puppeteer.launch({ headless: "new", userDataDir, args: ['--no-sandbox', '--disable-setuid-sandbox', '--window-size=1280,800'] }); try { const page = await browser.newPage(); await page.setRequestInterception(true); page.on('request', req => { if (['image', 'media', 'font'].includes(req.resourceType())) return req.abort().catch(() => {}); req.continue().catch(() => {}); }); page.on('response', async res => { const url = res.url(); if (url.includes('batchexecute')) { try { const text = await res.text(); // 监控验证码发送状态 if (text.includes('LookupVerifiedEmail') || text.includes('SendVerificationCode')) { console.log('✅ [网络监控] 监听到 SendVerificationCode 响应,服务器已发信!'); resolveCodeSent(); } // 监控验证码报错 if (text.includes('CAPTCHA_CHECK_FAILED')) { console.log('⚠️ [网络监控] 检测到人机验证码拦截,准备打码补丁...'); const newToken = await getCaptchaToken(apiKey); if (newToken) { const originalRequest = res.request(); const newPostData = patchPayload(originalRequest.postData(), newToken); console.log('🔄 [补丁] 正在重发带 Token 的 Payload...'); await page.evaluate(async (u, h, b) => { await fetch(u, { method: 'POST', headers: h, body: b }); }, url, originalRequest.headers(), newPostData); resolveAuthBarrier(); } } else if (text.includes('inner_api_status')) { resolveAuthBarrier(); } } catch (e) { } } }); console.log('🍪 正在设置初始 XSRF Cookie...'); await page.goto('https://auth.business.gemini.google/', { waitUntil: 'domcontentloaded' }); await page.setCookie( { name: '__Host-AP_SignInXsrf', value: 'KdLRzKwwBTD5wo8nUollAbY6cW0', domain: 'auth.business.gemini.google', path: '/', secure: true }, { name: '_GRECAPTCHA', value: '09ABCL...', domain: '.google.com', path: '/', secure: true } ); const targetUrl = `https://auth.business.gemini.google/login/email?continueUrl=https%3A%2F%2Fbusiness.gemini.google%2F&loginHint=${encodeURIComponent(email)}&xsrfToken=KdLRzKwwBTD5wo8nUollAbY6cW0`; console.log('🔗 正在加载登录页面...'); await page.goto(targetUrl, { waitUntil: 'domcontentloaded' }); console.log('🖱️ 正在点击 [通过电子邮件发送验证码] 按钮...'); await page.waitForSelector('#sign-in-with-email', { visible: true }); await page.click('#sign-in-with-email'); console.log('🔎 等待验证码输入框出现...'); const inputSelector = 'input[jsname="ovqh0b"]'; await page.waitForSelector(inputSelector, { timeout: 30000 }); console.log('⏳ [关键等待] 正在等待发信确认信号 (由网络响应触发)...'); const waitStart = Date.now(); await Promise.race([codeSentBarrier, sleep(12000)]); console.log(`⏱️ 等待结束,耗时: ${Date.now() - waitStart}ms`); console.log('📫 开始爬取邮箱验证码...'); const code = await startPollingForCode(email, clientId, refreshToken); if (!code) throw new Error("获取验证码超时"); console.log('⌨️ 正在输入验证码并提交...'); await page.type(inputSelector, code, { delay: 100 }); await page.click('button[jsname="XooR8e"]'); console.log('🔄 等待登录跳转...'); await sleep(4000); if (page.url().includes('/admin/create')) { console.log('📄 进入创建页面,检测是否有 [同意] 按钮...'); const btn = await page.waitForSelector('button.agree-button', { visible: true, timeout: 5000 }).catch(() => null); if (btn) { console.log('🖱️ 点击同意按钮'); await btn.click(); await sleep(2000); } } console.log('🍪 正在通过 CDP 协议提取全量 Cookie...'); let hostCoses = '', secureCSes = ''; for (let i = 0; i < 15; i++) { const client = await page.target().createCDPSession(); const { cookies } = await client.send('Network.getAllCookies'); await client.detach(); for (const c of cookies) { if (c.name === '__Host-C_OSES') hostCoses = c.value; if (c.name === '__Secure-C_SES') secureCSes = c.value; } if (hostCoses && secureCSes) { console.log('✨ 核心 Cookie 提取完毕'); break; } console.log(`📭 第 ${i+1} 次提取 Cookie 未果,继续轮询...`); await sleep(1000); if (i === 5) { console.log('🔄 提取超时,尝试跳转业务主域名促活 Cookie...'); await page.goto('https://business.gemini.google/', { waitUntil: 'networkidle2' }).catch(() => {}); } } if (!hostCoses || !secureCSes) throw new Error('Cookie 提取失败'); console.log('🏁 [任务成功] 正在返回结果'); return `${email}|${password}|${hostCoses.trim()}::${secureCSes.trim()}`; } finally { console.log('🧹 正在关闭浏览器并清理临时文件...'); await browser.close(); try { fs.rmSync(userDataDir, { recursive: true, force: true }); } catch (e) {} } } app.get('/login', async (req, res) => { const { data, apikey } = req.query; if (!data || !apikey) return res.status(400).send('Missing params'); try { const result = await runLoginTask(data, apikey); res.send(result); } catch (err) { console.error(`❌ 接口报错: ${err.message}`); res.status(500).send(`Error: ${err.message}`); } }); app.listen(port, () => { console.log(); console.log(`🚀 服务运行中: http://localhost:${port}`); console.log(`📝 请求示例: /login?data=email----pass----id----token&apikey=xxx`); console.log(); });

运行成功后请求示例为:
http://localhost:3000/login?data=CharlesHoward4117@outlook.com----rrom1652910----dbc8e03a-b00c-46bd-ae65-b683e7707cb0----M.C539_BL2.0.U.-Cn78r6PhJ0VI48KtRppemAxUI038pVKjd9Kyu*lxTsFW9cyZqou7zcWsKmg0DdlxCURrppr3paaCrZHMIY1gzBcD4p*p*TQHz4DqDF0xeUTOtkTlpy!B6leXglSy3O1Uj9hDUkq3f!75ha4A0VrC7agSXDzUaV!tPIJ7AA6fgdKeehLjzhNaHr5XqvTM39Gr1OR9H8VnYBB!K6yGDAhWWsYd5blEfhqVoAzG3TBDTcdrho3fwU2uR8V8oVtJfQ*aGyw9XwCj*gIRq1SdfWR5h!PSZ4TuFSJVqeLB7qbIOHLfWPtVkz*ya6jxMX4DqJASLQIIILno1lJznm1VAeQJPze!REpnD9L9F9t!dwRwpmd1235SbqnYAEG5rFXKSOqikRjSYSWeCVmZYBkWSWBLEuQ$&apikey=c169a2dbd972acac69b4f30c52c060bfbbe0854f84831

其中 data 就是邮箱的基本信息,这里只适配了微软邮箱
apikey 就是 yes 的 apikey

默认如果浏览器可以成功发送则不调用 yes 打码服务,返回示例

CharlesHoward4117@outlook.com|rrom1652910|COS.AW82PoFnmTPemeKaOLiqi59wSPBs5Q6mSkgqGsk2OwOP0c_vBgAo4L3k8IdN-La_tMrJbjbatQy9Uw6QllD2VL0xdhdbG8Ks2tlCGGEfxZgj3FhTP2fNSeEfk3ed1PL_l6oTfMiLL5ivKwKCYE5sbuI::CSE.AdwtfTBBmOWOWWkad3G32S_ut5NlmYWQhJCRohzHwevu9ZpYyKrRHpviThBTJNh8_KdlBLAUbSQT5zD6RGoggxO8air-J0l9qO_XtmbdLhXYDOCmMdNgDFq40G1HmpgeGfUtXEVB-cT2lf33J9xSx_F0w9ZmWrrI96KJk8ijQ_qc5T5nt3grRfI5B9YpHHOww_Uqo14z0Yg1899rHDdNpgg9y_X9xK3nDn8C3lYKerslcKak12VC_Rxq9JJDYNsysUjhfNmyt-tyU9xLaJV_vR_FG7nnkO7bmRL664yEpAzRD0dB9ZnxXrCb45q1Q7HRs3jjxoId1DVYw2aSbU5URDDY-q5bCDbHModdg36rRuICHmxQQRsz39YivNknIDe7kvOnubDYU1M9wmhX7bsLkKawILuj0jwsqNRb0jh-phY3D8DZhydEHROjjba99MAsIvkH8ZytY_x3Aw

COS 与 CSE 的 cookie 都有返回,一个账户大概 30s 左右,可并发,一分钟基本可以刷新或者注册 40 个账户(并发 20)

欢迎更多开发者去完善自动化注册和刷新的逻辑…


📌 转载信息
转载时间:
2026/1/3 12:07:27