Toda integração com CAPTCHA começa igual: você abre o HTML da página de teste para achar a sitekey, cola em um cliente REST, espera o token e ainda volta ao painel para conferir o saldo. São quatro janelas para uma tarefa que caberia em um atalho.
Esse ciclo cabe dentro do editor. Com um manifesto e cerca de 250 linhas de JavaScript, você registra comandos que enviam o desafio à API da CaptchaAI, consultam o resultado, copiam o token e mantêm o saldo na barra de status. O código completo está abaixo.
Por que resolver o CAPTCHA dentro do VS Code
A extensão não substitui a API: encurta a distância entre o editor e o endpoint. Três ganhos aparecem na primeira semana:
- Menos troca de contexto. A sitekey sai do arquivo aberto e o token volta para o cursor.
- Saldo à vista. A barra de status evita descobrir tarde que os créditos acabaram no meio de uma suíte de QA.
- Um padrão único. Os snippets fixam a mesma forma de chamar
in.phperes.phpno time inteiro.
Recursos que a extensão expõe
| Recurso | O que faz |
|---|---|
| Saldo na barra de status | Mostra o saldo da conta sem abrir o painel |
| Comando de resolução | Envia um desafio CAPTCHA direto do editor |
| Detecção de sitekey | Extrai sitekeys dos arquivos abertos |
| Snippets | Inserem o padrão de chamada para reCAPTCHA, Turnstile e GeeTest v3 |
| Consulta de erros | Mostra a descrição do código de erro ao passar o mouse |
Estrutura de arquivos do projeto
Comece pelo esqueleto mínimo: manifesto, um arquivo de código e a pasta de snippets.
captchaai-vscode/
├── package.json
├── src/
│ └── extension.js
├── snippets/
│ ├── python.json
│ └── javascript.json
└── README.md
O manifesto: o que declarar no package.json
Resolva três pontos no manifesto antes de escrever qualquer lógica:
- Evento de ativação.
onStartupFinishedmantém a extensão fora do caminho crítico de abertura do editor. - Comandos contribuídos. Um comando por ação facilita atribuir atalhos depois.
- Configuração segura. A chave de API entra como configuração, nunca embutida no comando.
O objetivo é a menor superfície de package.json que ainda permite testar e publicar localmente:
{
"name": "captchaai-dev-tools",
"displayName": "CaptchaAI Dev Tools",
"description": "CaptchaAI API development tools for VS Code",
"version": "1.0.0",
"engines": { "vscode": "^1.80.0" },
"categories": ["Snippets", "Other"],
"activationEvents": ["onStartupFinished"],
"main": "./src/extension.js",
"contributes": {
"commands": [
{
"command": "captchaai.checkBalance",
"title": "CaptchaAI: Check Balance"
},
{
"command": "captchaai.solveRecaptcha",
"title": "CaptchaAI: Solve reCAPTCHA v2"
},
{
"command": "captchaai.solveTurnstile",
"title": "CaptchaAI: Solve Turnstile"
},
{
"command": "captchaai.detectSitekey",
"title": "CaptchaAI: Detect Sitekey in File"
}
],
"configuration": {
"title": "CaptchaAI",
"properties": {
"captchaai.apiKey": {
"type": "string",
"default": "",
"description": "Your CaptchaAI API key"
},
"captchaai.showBalance": {
"type": "boolean",
"default": true,
"description": "Show balance in status bar"
},
"captchaai.pollInterval": {
"type": "number",
"default": 5,
"description": "Poll interval in seconds"
}
}
},
"snippets": [
{
"language": "python",
"path": "./snippets/python.json"
},
{
"language": "javascript",
"path": "./snippets/javascript.json"
}
]
}
}
A lógica principal: saldo, envio e consulta do resultado
O arquivo src/extension.js tem três responsabilidades: a rotina de saldo chama res.php com action=getbalance e reescreve a barra de status a cada cinco minutos; o comando de resolução pede sitekey e pageurl, envia a tarefa para in.php e entra no loop de consulta; a detecção varre o arquivo aberto com quatro expressões regulares.
Dois detalhes importam: o loop respeita cancellation.isCancellationRequested, então dá para abortar uma resolução travada pela notificação de progresso, e o intervalo entre consultas vem de captchaai.pollInterval.
// src/extension.js
const vscode = require("vscode");
const API_BASE = "https://ocr.captchaai.com";
function getApiKey() {
const config = vscode.workspace.getConfiguration("captchaai");
const key = config.get("apiKey");
if (!key) {
vscode.window.showErrorMessage(
"CaptchaAI: Set your API key in Settings → CaptchaAI"
);
return null;
}
return key;
}
// --- Balance Status Bar ---
let balanceStatusBar;
let balanceInterval;
async function updateBalance() {
const key = getApiKey();
if (!key) return;
try {
const url = new URL(`${API_BASE}/res.php`);
url.searchParams.set("key", key);
url.searchParams.set("action", "getbalance");
url.searchParams.set("json", "1");
const response = await fetch(url);
const result = await response.json();
if (result.status === 1) {
const balance = parseFloat(result.request).toFixed(2);
balanceStatusBar.text = `$(credit-card) CaptchaAI: $${balance}`;
balanceStatusBar.tooltip = `CaptchaAI Balance: $${balance}`;
} else {
balanceStatusBar.text = "$(warning) CaptchaAI: Error";
}
} catch {
balanceStatusBar.text = "$(warning) CaptchaAI: Offline";
}
}
// --- Solve Command ---
async function solveCaptcha(method, extraFields) {
const key = getApiKey();
if (!key) return;
const sitekey = await vscode.window.showInputBox({
prompt: "Enter the CAPTCHA sitekey",
placeHolder: "6LeIxAcTAAAAAJcZ...",
});
if (!sitekey) return;
const pageurl = await vscode.window.showInputBox({
prompt: "Enter the page URL",
placeHolder: "https://example.com",
});
if (!pageurl) return;
const params = {
key,
method,
pageurl,
json: 1,
...extraFields,
};
if (method === "userrecaptcha") {
params.googlekey = sitekey;
} else {
params.sitekey = sitekey;
}
// Submit
vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: "CaptchaAI: Solving...",
cancellable: true,
},
async (progress, cancellation) => {
try {
const submitResponse = await fetch(`${API_BASE}/in.php`, {
method: "POST",
body: new URLSearchParams(params),
});
const submitResult = await submitResponse.json();
if (submitResult.status !== 1) {
vscode.window.showErrorMessage(
`CaptchaAI: ${submitResult.request || "Submit failed"}`
);
return;
}
const taskId = submitResult.request;
progress.report({ message: `Task ${taskId} submitted` });
// Poll
const config = vscode.workspace.getConfiguration("captchaai");
const interval = config.get("pollInterval") * 1000;
for (let i = 0; i < 60; i++) {
if (cancellation.isCancellationRequested) return;
await new Promise((r) => setTimeout(r, interval));
const pollUrl = new URL(`${API_BASE}/res.php`);
pollUrl.searchParams.set("key", key);
pollUrl.searchParams.set("action", "get");
pollUrl.searchParams.set("id", taskId);
pollUrl.searchParams.set("json", "1");
const pollResponse = await fetch(pollUrl);
const pollResult = await pollResponse.json();
if (pollResult.request === "CAPCHA_NOT_READY") {
progress.report({ message: `Waiting... (${(i + 1) * (interval / 1000)}s)` });
continue;
}
if (pollResult.status === 1) {
const token = pollResult.request;
// Copy to clipboard
await vscode.env.clipboard.writeText(token);
vscode.window.showInformationMessage(
`CaptchaAI: Solved! Token copied to clipboard (${token.length} chars)`
);
// Also insert at cursor if editor is active
const editor = vscode.window.activeTextEditor;
if (editor) {
const action = await vscode.window.showQuickPick(
["Copy only", "Insert at cursor"],
{ placeHolder: "Token copied. Insert into editor?" }
);
if (action === "Insert at cursor") {
editor.edit((editBuilder) => {
editBuilder.insert(editor.selection.active, token);
});
}
}
return;
}
vscode.window.showErrorMessage(
`CaptchaAI: ${pollResult.request || "Solve failed"}`
);
return;
}
vscode.window.showErrorMessage("CaptchaAI: Solve timed out");
} catch (err) {
vscode.window.showErrorMessage(`CaptchaAI: ${err.message}`);
}
}
);
}
// --- Sitekey Detection ---
async function detectSitekey() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage("No active editor");
return;
}
const text = editor.document.getText();
const patterns = [
{ regex: /data-sitekey=["']([^"']+)["']/g, type: "HTML data-sitekey" },
{ regex: /googlekey['":\s]+["']([a-zA-Z0-9_-]{40})["']/g, type: "API googlekey" },
{ regex: /sitekey['":\s]+["']([a-zA-Z0-9_-]{20,})["']/g, type: "sitekey parameter" },
{ regex: /render=([a-zA-Z0-9_-]{40})/g, type: "reCAPTCHA render" },
];
const found = [];
for (const { regex, type } of patterns) {
let match;
while ((match = regex.exec(text)) !== null) {
found.push({ key: match[1], type, position: match.index });
}
}
if (found.length === 0) {
vscode.window.showInformationMessage("No sitekeys found in current file");
return;
}
const items = found.map((f) => ({
label: f.key,
description: f.type,
detail: `Position: ${f.position}`,
key: f.key,
}));
const selected = await vscode.window.showQuickPick(items, {
placeHolder: `Found ${found.length} sitekey(s) — select to copy`,
});
if (selected) {
await vscode.env.clipboard.writeText(selected.key);
vscode.window.showInformationMessage(`Sitekey copied: ${selected.key}`);
}
}
// --- Activation ---
function activate(context) {
// Balance status bar
const config = vscode.workspace.getConfiguration("captchaai");
if (config.get("showBalance")) {
balanceStatusBar = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
100
);
balanceStatusBar.command = "captchaai.checkBalance";
balanceStatusBar.text = "$(credit-card) CaptchaAI";
balanceStatusBar.show();
updateBalance();
balanceInterval = setInterval(updateBalance, 300000); // Every 5 minutes
context.subscriptions.push(balanceStatusBar);
}
// Register commands
context.subscriptions.push(
vscode.commands.registerCommand("captchaai.checkBalance", async () => {
await updateBalance();
vscode.window.showInformationMessage(balanceStatusBar.tooltip);
}),
vscode.commands.registerCommand("captchaai.solveRecaptcha", () => {
solveCaptcha("userrecaptcha", {});
}),
vscode.commands.registerCommand("captchaai.solveTurnstile", () => {
solveCaptcha("turnstile", {});
}),
vscode.commands.registerCommand("captchaai.detectSitekey", detectSitekey)
);
}
function deactivate() {
if (balanceInterval) clearInterval(balanceInterval);
}
module.exports = { activate, deactivate };
CAPCHA_NOT_READY não é erro: é a API avisando que a tarefa ainda está na fila. O token só existe depois de status === 1 — e aí vai para a área de transferência.
Snippets para Python e JavaScript
Os comandos servem ao teste pontual; os snippets, ao código que fica no repositório. Salve os dois arquivos em snippets/: o prefixo cai- insere o padrão completo e a chave é digitada uma vez só.
Snippets de Python
{
"CaptchaAI reCAPTCHA v2": {
"prefix": "cai-recaptcha-v2",
"body": [
"import requests",
"",
"# Submit reCAPTCHA v2 task",
"response = requests.post(",
" \"https://ocr.captchaai.com/in.php\",",
" data={",
" \"key\": \"${1:YOUR_API_KEY}\",",
" \"method\": \"userrecaptcha\",",
" \"googlekey\": \"${2:SITE_KEY}\",",
" \"pageurl\": \"${3:https://example.com}\",",
" \"json\": 1,",
" },",
")",
"task_id = response.json()[\"request\"]",
"",
"# Poll for result",
"import time",
"while True:",
" time.sleep(5)",
" result = requests.get(",
" \"https://ocr.captchaai.com/res.php\",",
" params={\"key\": \"${1}\", \"action\": \"get\", \"id\": task_id, \"json\": 1},",
" ).json()",
" if result[\"request\"] != \"CAPCHA_NOT_READY\":",
" token = result[\"request\"]",
" break"
],
"description": "CaptchaAI reCAPTCHA v2 solve"
},
"CaptchaAI Turnstile": {
"prefix": "cai-turnstile",
"body": [
"import requests",
"",
"response = requests.post(",
" \"https://ocr.captchaai.com/in.php\",",
" data={",
" \"key\": \"${1:YOUR_API_KEY}\",",
" \"method\": \"turnstile\",",
" \"sitekey\": \"${2:SITE_KEY}\",",
" \"pageurl\": \"${3:https://example.com}\",",
" \"json\": 1,",
" },",
")",
"task_id = response.json()[\"request\"]"
],
"description": "CaptchaAI Turnstile solve"
},
"CaptchaAI Balance Check": {
"prefix": "cai-balance",
"body": [
"import requests",
"",
"balance = requests.get(",
" \"https://ocr.captchaai.com/res.php\",",
" params={\"key\": \"${1:YOUR_API_KEY}\", \"action\": \"getbalance\", \"json\": 1},",
").json()",
"print(f\"Balance: \\${balance['request']}\")"
],
"description": "CaptchaAI balance check"
}
}
Snippets de JavaScript
{
"CaptchaAI reCAPTCHA v2": {
"prefix": "cai-recaptcha-v2",
"body": [
"const response = await fetch('https://ocr.captchaai.com/in.php', {",
" method: 'POST',",
" body: new URLSearchParams({",
" key: '${1:YOUR_API_KEY}',",
" method: 'userrecaptcha',",
" googlekey: '${2:SITE_KEY}',",
" pageurl: '${3:https://example.com}',",
" json: 1,",
" }),",
"});",
"const { request: taskId } = await response.json();",
"",
"// Poll for result",
"let token;",
"while (true) {",
" await new Promise(r => setTimeout(r, 5000));",
" const url = new URL('https://ocr.captchaai.com/res.php');",
" url.searchParams.set('key', '${1}');",
" url.searchParams.set('action', 'get');",
" url.searchParams.set('id', taskId);",
" url.searchParams.set('json', '1');",
" const result = await (await fetch(url)).json();",
" if (result.request !== 'CAPCHA_NOT_READY') {",
" token = result.request;",
" break;",
" }",
"}"
],
"description": "CaptchaAI reCAPTCHA v2 solve"
}
}
Threads, plano e um cenário de time
Imagine um time de QA em São Paulo que testa formulários em staging.example.com: duas ou três pessoas usam o comando ao longo do dia e um pipeline noturno dispara dezenas de desafios em paralelo. Como a CaptchaAI cobra por thread simultânea, com resoluções ilimitadas por thread, a conta é de concorrência, não de volume. O uso no editor é intermitente e cabe no plano BASIC (US$ 15/mês, 5 threads); quem define a faixa é o pipeline — STANDARD (US$ 30/mês, 15 threads) ou ADVANCE (US$ 90/mês, 50 threads), conforme o paralelismo da suíte.
Dois cuidados fecham o cenário: mantenha os workers na região mais próxima do ambiente de teste (sa-east-1 reduz o RTT no Brasil) e não registre tokens nem conteúdo de formulário nos logs. Se o QA toca dados pessoais reais, as obrigações da LGPD — RGPD em Portugal — valem também na máquina do desenvolvedor.
Solução de problemas
| Problema | Causa provável | Correção |
|---|---|---|
| Saldo aparece como "Offline" | Editor não alcança a API | Verifique rede e firewall; confirme que ocr.captchaai.com responde |
| Erro "Defina sua chave de API" | Chave não configurada | Configurações → busque "CaptchaAI" → informe a chave |
| Snippets não aparecem | Modo de linguagem errado | Confirme se o modo do arquivo corresponde ao snippet |
| Resolução estoura o tempo limite | Tarefa falhou ou rede lenta | Aumente o intervalo de polling; revise sitekey e pageurl |
| Detecção não encontra sitekey | Nenhum padrão compatível | Verifique se o arquivo traz data-sitekey, googlekey ou sitekey |
Como instalar a extensão do VS Code no time
Para uso local, empacote com vsce package e instale o arquivo gerado: code --install-extension captchaai-dev-tools-1.0.0.vsix. Para o time, versione esse .vsix em um repositório interno. Para publicar de fato, siga o guia oficial de publicação de extensões.
Perguntas frequentes
Onde guardar a chave de API sem deixá-la em texto puro?
As configurações do VS Code ficam em JSON no disco, legíveis por qualquer processo do usuário. Troque a leitura de configuration pela API SecretStorage, que grava a chave no keychain do sistema operacional. Em CI, prefira variável de ambiente.
A extensão funciona no VS Code Insiders, no code-server e em forks?
Sim, desde que o engines.vscode declarado seja compatível. O código usa apenas APIs estáveis (window, commands, workspace, env.clipboard). Em ambientes remotos, confirme que a máquina que executa a extensão alcança ocr.captchaai.com.
Preciso da extensão para usar a API da CaptchaAI?
Não. A API é HTTP puro: in.php para enviar a tarefa, res.php para consultar o resultado. A extensão é conveniência de desenvolvimento — o mesmo par de requisições funciona em cURL, Python ou Node.js.
Quantas threads o comando de resolução consome?
Uma por desafio em andamento. Enquanto a notificação de progresso está aberta, aquela thread está ocupada; quando o token chega, ela volta para o pool. Várias pessoas resolvendo ao mesmo tempo somam threads simultâneas.
E quanto ao hCaptcha e ao FunCaptcha?
Não são suportados pela CaptchaAI, então não crie comandos para eles. Exponha os tipos disponíveis: reCAPTCHA v2 e v3 (inclusive Enterprise), Cloudflare Turnstile e Challenge, GeeTest v3, imagem/OCR e grade de imagens. CaptchaFox, Friendly Captcha e Lemin estão em beta; o GeeTest v4 aparece como em breve.
Artigos relacionados
- Proteja a chave de API com lista de IPs permitidos
- CaptchaAI e CapMonster Cloud lado a lado
- Webhook ou polling: como receber o token
Próximas etapas
Traga a resolução de CAPTCHA para onde você já escreve o código — crie sua chave de API e teste o primeiro comando ainda hoje.
Guias relacionados: