Referencia

Extensión de VS Code para desarrollar con la API de CaptchaAI

¿Merece la pena crear una extensión de VS Code solo para trabajar con CaptchaAI? Si integras la API a diario, sí: dejas de saltar entre el editor, la terminal y el panel de control para copiar sitekeys, comprobar tu saldo o lanzar una resolución de prueba. Una extensión reúne esas tareas dentro del propio editor y, en esta referencia, la construyes pieza a pieza con el código listo para copiar.

Qué aporta una extensión de VS Code para CaptchaAI

Antes de escribir una línea, conviene fijar el alcance. La extensión no reemplaza a tu backend: es una capa de comodidad para las fases de desarrollo y QA. Estas son sus funciones principales:

Función Qué hace
Saldo en la barra de estado Muestra tu saldo actual de CaptchaAI de un vistazo
Comando de resolución Envía una resolución de CAPTCHA directamente desde VS Code
Detección de sitekeys Resalta y extrae los sitekeys de los archivos abiertos
Snippets de código Inserta plantillas para llamadas a la API de reCAPTCHA, Turnstile y GeeTest v3
Búsqueda de errores Pasa el cursor sobre un código de error para ver su descripción

Estructura del proyecto

El proyecto sigue el diseño estándar de una extensión de VS Code:

  • package.json — el manifiesto con comandos, ajustes y snippets.
  • src/extension.js — la lógica que se ejecuta en el editor.
  • snippets/ — un archivo JSON por lenguaje.
captchaai-vscode/
├── package.json
├── src/
│   └── extension.js
├── snippets/
│   ├── python.json
│   └── javascript.json
└── README.md

package.json: el manifiesto de la extensión

El package.json declara tres cosas que la extensión aporta a VS Code:

  • Los cuatro comandos de la paleta.
  • El campo para tu API key y el intervalo de sondeo por defecto.
  • Los archivos de snippets, uno por lenguaje.
{
  "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"
      }
    ]
  }
}

La lógica principal en src/extension.js

El corazón de la extensión vive en src/extension.js. Este archivo lee la API key de la configuración, pinta el saldo en la barra de estado, registra los comandos y ejecuta el ciclo de envío y sondeo contra los endpoints in.php y res.php. Observa cómo el comando de resolución reutiliza el pollInterval que definiste en los ajustes y copia el token al portapapeles en cuanto llega.

// 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 };

Dos parámetros que confunden al principio y que esta función resuelve por ti:

  • Para reCAPTCHA, el sitekey viaja en googlekey.
  • Para Turnstile, viaja en sitekey.

La función solveCaptcha elige el nombre correcto según el método, así que evitas el error más común de la primera integración.

Versión mínima del package.json

Si solo quieres los comandos y los snippets, sin la barra de estado ni todas las opciones de configuración, este package.json reducido basta para arrancar.

{
  "contributes": {
    "commands": [
      { "command": "captchaai.solveRecaptcha", "title": "CaptchaAI: Solve reCAPTCHA" },
      { "command": "captchaai.solveTurnstile", "title": "CaptchaAI: Solve Turnstile" },
      { "command": "captchaai.detectSitekey", "title": "CaptchaAI: Detect Sitekey" }
    ],
    "configuration": {
      "title": "CaptchaAI",
      "properties": {
        "captchaai.apiKey": { "type": "string" },
        "captchaai.pollInterval": { "type": "number", "default": 5 }
      }
    }
  }
}

Snippets de código

Los snippets son, en la práctica, la función que más se usa a diario. Escribes un prefijo y VS Code expande el patrón completo con marcadores de posición que recorres con el tabulador. Los tres que más usarás:

  • cai-recaptcha-v2 — envío y sondeo de un reCAPTCHA v2.
  • cai-turnstile — envío de un Turnstile.
  • cai-balance — consulta rápida de saldo.

Snippets de Python

El snippet de Python cubre el flujo entero: enviar la tarea, sondear el resultado y quedarte con el token. El marcador ${1:YOUR_API_KEY} se rellena una sola vez y se propaga a todas sus ocurrencias.

{
  "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

La versión en JavaScript sigue el mismo patrón con fetch y URLSearchParams, cómoda si tu backend corre sobre Node.js.

{
  "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"
  }
}

Caso real: CaptchaAI en VS Code para una agencia de QA

Piensa en un equipo de tres desarrolladores en Bogotá que mantiene flujos de checkout de prueba para varios clientes de e-commerce. Antes comprobaban el saldo entrando al panel web varias veces al día; con la extensión, el saldo vive en la barra de estado y una resolución de prueba está a un atajo de teclado. Para ese volumen de desarrollo, el plan BASIC ($15/mes, 5 threads) cubre el día a día, y el coste fijo mensual en USD les resulta más predecible que pagar por resolución al presupuestar proyectos en pesos. El matiz es local: para quien factura en monedas volátiles, un gasto fijo en dólares se traslada mejor a un presupuesto.

Solución de problemas comunes

Cuando algo no funciona, casi siempre es una de estas cinco causas.

Problema Causa Solución
El saldo muestra "Offline" VS Code no alcanza la API Revisa la red o el firewall; asegúrate de que ocr.captchaai.com sea accesible
Error "Set your API key" La clave no está configurada Ajustes → busca "CaptchaAI" → introduce tu API key
Los snippets no aparecen Modo de lenguaje incorrecto Comprueba que el modo de lenguaje del archivo coincida con el snippet (Python o JavaScript)
La resolución agota el tiempo de espera Tarea fallida o red lenta Aumenta el intervalo de sondeo en los ajustes; verifica el sitekey y la URL de la página
La detección de sitekeys no encuentra nada No hay patrones que coincidan Confirma que el archivo contenga atributos data-sitekey, googlekey o sitekey

Preguntas frecuentes

¿Funciona la extensión en Cursor, VSCodium y otros editores basados en VS Code?

Sí, en la mayoría. Cursor, VSCodium y Windsurf comparten la misma API de extensiones, así que los comandos y los snippets se comportan igual. La única diferencia habitual es el marketplace: en editores que no usan el de Microsoft tendrás que instalar el .vsix a mano con code --install-extension.

¿Consume threads o saldo tener la extensión abierta en el editor?

No por sí sola. La barra de estado solo llama a getbalance, que no descuenta saldo. Únicamente gastas cuando lanzas una resolución real con el comando de resolver; cada tarea en curso ocupa uno de los threads de tu plan hasta que termina.

¿Qué tipos de CAPTCHA puedo resolver directamente desde el editor?

Los comandos incluidos cubren reCAPTCHA v2 y Cloudflare Turnstile, y los snippets añaden GeeTest v3. Puedes ampliar la extensión a cualquier tipo que resuelva la API cambiando el parámetro method. Ten en cuenta que CaptchaAI no resuelve hCaptcha ni FunCaptcha, así que no añadas comandos para esos tipos.

¿Cómo guardo la API key sin dejarla en texto plano?

Los ajustes de VS Code se guardan como JSON en disco, en claro. Para mayor seguridad, usa la API SecretStorage de VS Code, que almacena la clave en el llavero del sistema operativo. Y nunca subas un settings.json con la clave a un repositorio compartido: añádelo a .gitignore.

Artículos relacionados

Próximos pasos

Lleva CaptchaAI a tu editor: consigue tu API key, pégala en los ajustes y resuelve tu primer CAPTCHA sin salir de VS Code.

Guías relacionadas:

Los comentarios están deshabilitados para este artículo.