أربعة أوامر داخل المحرر تغني عن معظم ما تفعله يدويًا أثناء دمج CaptchaAI: التقاط مفتاح الموقع من الملف المفتوح، إرسال مهمة حل، متابعة النتيجة، ثم لصق الرمز في مكانه. هذه المقالة تبني تلك الأوامر في إضافة VS Code واحدة مكتوبة بـ JavaScript، مع شريط رصيد ومقتطفات جاهزة لـ Python وJavaScript.
الدافع عملي. من يدمج CAPTCHA يتنقل بين ثلاث نوافذ: المحرر، والمتصفح الذي يعرض data-sitekey، وأداة الطلبات التي يجرّب فيها in.php وres.php. كل انتقال يكسر التركيز ويرفع احتمال نسخ مفتاح خاطئ.
ما الذي تضيفه الإضافة إلى VS Code
| العنصر | ما يقدّمه فعليًا |
|---|---|
| شريط الرصيد | يعرض رصيد CaptchaAI في زاوية النافذة ويحدّثه دوريًا |
| أمر الحل | يرسل مهمة reCAPTCHA v2 أو Turnstile ويعيد الرمز جاهزًا |
| كشف مفتاح الموقع | يستخرج كل sitekey من الملف المفتوح |
| المقتطفات | قوالب جاهزة لاستدعاءات reCAPTCHA وTurnstile وGeeTest v3 |
| قراءة رموز الخطأ | تفسير مباشر لرموز الخطأ دون فتح التوثيق |
كل ذلك يجري عبر نقطتي النهاية نفسيهما في أي تكامل: in.php للإرسال وres.php للنتيجة والرصيد. الإضافة واجهة مريحة فوق الـ API نفسه، فأي منطق تكتبه هنا يبقى صالحًا لسكربتاتك الإنتاجية.
متى تستحق الإضافة العناء ومتى يكفي سكربت صغير
القاعدة بسيطة: كلما تكرر فتحك لملفات تحوي مفاتيح مواقع ارتفعت قيمة الإضافة، وما يعمل بلا تدخل بشري مكانه سكربت مستقل.
| الحالة | إضافة داخل VS Code | سكربت مستقل |
|---|---|---|
| فحص مفتاح موقع في ملف مفتوح | الأفضل | يجبرك على مغادرة المحرر |
| طلب API مرة كل بضعة أيام | زائد عن الحاجة | الأفضل |
| فريق كامل يحتاج الأدوات نفسها | الأفضل | توزيع يدوي على كل جهاز |
| مهام مجدولة أو خطوات CI | غير مناسبة | الأفضل |
سيناريو من فرق المنطقة: بيئات متعددة ومفتاح واحد
تخيّل فريق QA في الرياض يختبر نموذج تسجيل محميًا بـ Cloudflare Turnstile على ثلاث بيئات: التطوير وما قبل الإنتاج والإنتاج. لكل بيئة مفتاح موقع مختلف، وثلاثتها في ملفات إعداد داخل المستودع نفسه، فكان كل اختبار يبدأ بفتح المتصفح ونسخ المفتاح من البيئة الصحيحة.
بعد التثبيت يفتح المهندس ملف الإعداد ويشغّل أمر الكشف، فتظهر المفاتيح الثلاثة مع موضع كل واحد، ثم يشغّل أمر الحل وينتظر إشعار نسخ الرمز. الفارق ليس تقنيًا بل في عدد النوافذ: من ثلاث إلى واحدة.
أما تشغيليًا فتُحسب خطط CaptchaAI بعدد الـ threads المتزامنة لا بعدد عمليات الحل، وكل خطة تشمل عمليات حل غير محدودة خلال الشهر. خطة BASIC بسعر 15 دولارًا شهريًا تتيح 5 threads وتكفي فريقًا صغيرًا يختبر بالتناوب، أما خطة ADVANCE بسعر 90 دولارًا شهريًا فتوفّر 50 thread وتناسب الفرق التي تشغّل اختبارات آلية بالتوازي. الأسعار بالدولار الأمريكي.
بنية مشروع الإضافة
المشروع كله خمسة ملفات: بيان يصف الأوامر والإعدادات، ملف منطق واحد، وملفا مقتطفات. لا حاجة إلى أدوات بناء ولا إلى TypeScript حتى تستقر الفكرة.
captchaai-vscode/
├── package.json
├── src/
│ └── extension.js
├── snippets/
│ ├── python.json
│ └── javascript.json
└── README.md
بيان الإضافة: تسجيل الأوامر والإعدادات
ملف package.json هو العقد بين الإضافة والمحرر: فيه تُعلن الأوامر الأربعة، وثلاثة إعدادات — مفتاح الـ API، إظهار شريط الرصيد، والفاصل بين محاولات استطلاع النتيجة. ابدأ بالقيمة الافتراضية 5 ثوانٍ وارفعها مع الاتصال البطيء. أما onStartupFinished فيجعل الإضافة تُقلع مع المحرر دون تأخير فتح الملفات.
{
"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"
}
]
}
}
منطق إضافة CaptchaAI: الرصيد والحل وكشف المفاتيح
الملف التالي ثلاث كتل مستقلة يسهل تعديل كل واحدة على حدة:
- قراءة الرصيد. استعلام إلى
res.phpبالإجراءgetbalanceثم كتابة النتيجة في شريط الحالة، وتتكرر كل خمس دقائق عبر مؤقّت يُلغى عند تعطيل الإضافة. - إرسال المهمة واستطلاع النتيجة. بعد سؤال المستخدم عن مفتاح الموقع وعنوان الصفحة، يُرسَل الطلب إلى
in.php، ثم يدور الفحص الدوري علىres.phpحتى يختفي الردCAPCHA_NOT_READY. لاحظ اختلاف اسم الحقل:googlekeyلعائلة reCAPTCHA وsitekeyلـ Turnstile، وهو أشيع مصدر خطأ في التكاملات الأولى. - كشف مفاتيح الموقع. أربعة أنماط regex تغطي
data-sitekey، وgooglekey، وأي حقل باسمsitekey، ومعاملrenderفي reCAPTCHA v3.
شريط التقدّم قابل للإلغاء: عند الإلغاء تتوقف حلقة الفحص بدل أن تشغل thread من خطتك.
// 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 };
مقتطفات جاهزة تكتب التكامل نيابة عنك
المقتطفات هي الجزء الأكثر استخدامًا، لأنها لا تتطلب شبكة ولا مفتاحًا مضبوطًا. اكتب البادئة في ملف بلغة مطابقة فتظهر بنية الاستدعاء كاملة بحقول تتنقل بينها بمفتاح Tab: مفتاح الـ API، ثم مفتاح الموقع، ثم عنوان الصفحة. وتوحيدها في الفريق يُنهي التباين بين مطوّر يكتب حلقة فحص منضبطة وآخر يتركها بلا حد.
مقتطفات 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"
}
}
مقتطفات JavaScript وNode.js
النسخة نفسها بأسلوب fetch غير المتزامن، مناسبة لسكربتات Node.js. أبقِ البادئة مطابقة لنظيرتها في Python حتى يتذكر الفريق أمرًا واحدًا لا اثنين.
{
"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"
}
}
تشغيل إضافة CaptchaAI على أجهزة الفريق
لست مضطرًا لنشر الإضافة في متجر VS Code كي يستخدمها زملاؤك: احزم المشروع في ملف .vsix ووزّعه داخليًا، فيثبّته كل مطوّر بأمر code --install-extension واحد.
وقبل التوزيع اضبط ثلاثًا: مفتاح الـ API في إعدادات المستخدم لا مساحة العمل، ورقم إصدار واضح لتتبّع النسخ، وخطوة تثبيت واحدة في README.
أخطاء شائعة وكيف تتصرف معها
| العَرَض | السبب المرجّح | الإجراء |
|---|---|---|
| شريط الرصيد لا يظهر | خيار captchaai.showBalance معطّل أو الإضافة لم تُفعّل |
فعّل الخيار وأعد تحميل النافذة |
| الرصيد يظهر بحالة "Offline" | جدار حماية أو خادم وسيط يمنع الوصول | تأكد من الوصول إلى ocr.captchaai.com من الجهاز |
| أمر الحل يعيد خطأ فورًا | مفتاح الـ API غير مضبوط أو العنوان ناقص | أضف المفتاح واستخدم عنوانًا كاملًا يبدأ بـ https |
| المقتطفات لا تظهر | وضع اللغة في الملف لا يطابق ملف المقتطفات | بدّل وضع اللغة إلى Python أو JavaScript |
| لا يُعثر على أي sitekey | المفتاح يُحقن عبر JavaScript ديناميكي أو داخل iframe | وسّع أنماط regex أو التقطه من أدوات المطوّر |
| انتهاء المهلة قبل وصول الرمز | شبكة بطيئة أو مفتاح موقع غير صالح | ارفع captchaai.pollInterval وتحقق من المفتاح |
| الرمز يُنسخ ولا يُدرج | لا يوجد محرر نشط أو المؤشر خارج الموضع | افتح الملف الهدف ثم اختر Insert at cursor |
أسئلة شائعة
هل تعمل الإضافة في بيئات Remote وWSL وحاويات Docker؟
نعم، فالمنطق كله يعمل داخل المضيف الذي تتصل به. تذكّر أن الاتصال بالـ API يخرج من تلك البيئة لا من جهازك، فلا بد أن تسمح شبكتها بالوصول الخارجي وأن يكون المفتاح مضبوطًا هناك.
ما أنواع CAPTCHA التي يمكن تشغيلها فعليًا عبر هذه الأوامر؟
الأنواع المتاحة عمومًا تشمل reCAPTCHA v2 وv3 بنسخها، وCloudflare Turnstile وCloudflare Challenge، وGeeTest v3، والصور والشبكات، وBLS. وثلاثة أنواع في مرحلة بيتا: CaptchaFox وFriendly Captcha وLemin. في المقابل لا تدعم الخدمة hCaptcha ولا FunCaptcha، أما GeeTest v4 فلم يصبح متاحًا بعد.
أين أخزّن مفتاح الـ API حتى لا ينتهي داخل مستودع Git؟
إعدادات مساحة العمل تُحفظ في مجلد .vscode داخل المشروع، وهو ما يُرفَع عادة مع الكود. ضع المفتاح في إعدادات المستخدم، أو استخدم واجهة SecretStorage لتخزينه في سلسلة مفاتيح نظام التشغيل. وإن استخدمت متغير بيئة، أضف ملفه إلى .gitignore قبل أول commit لا بعده.
هل يستهلك الحل من داخل المحرر حصة من خطتي؟
كل عملية حل تشغل thread واحدًا حتى تعود النتيجة ثم يتحرر لغيرها. الاختبار اليدوي من المحرر يشغل thread واحدًا في اللحظة، فنادرًا ما يزاحم اختباراتك الآلية إلا قرب سقف خطتك. وإن لاحظت انتظارًا غير معتاد، راجع المهام المتزامنة قبل افتراض بطء الخدمة.
الخطوات التالية
- البدء السريع مع CaptchaAI
- حلّ reCAPTCHA v2 عبر الـ API
- التعامل مع Cloudflare Turnstile
- دليل حل GeeTest v3 بالـ API