التكاملات

Airtable + CaptchaAI: تشغيل حل CAPTCHA من داخل قاعدة البيانات

عمود من الروابط في قاعدة Airtable، وكل رابط محجوب خلف reCAPTCHA v2 قبل أن تصل إلى بياناته — هذا هو الموقف الذي يعالجه هذا الدليل من دون سطر واحد من بنية تحتية خارجية. تستطيع أتمتة Airtable وأداة Scripting أن تتوليا الدورة كاملة: تلتقطان السجل، وترسلان معلمات reCAPTCHA v2 إلى CaptchaAI، وتحفظان الرمز المحلول في العمود المجاور، بلا خادم ولا Webhook وسيط.

في الأسطر التالية نبني هذا المسار خطوة بخطوة:

  • جدول مهام مضبوط الحقول يعمل قائمةَ انتظار للحل.
  • أتمتة تُطلَق لحظة وصول سجل بحالة pending.
  • سكربت للحل الفردي وآخر للمعالجة الدُفعية حين تتراكم عشرات الروابط دفعة واحدة.

جوهر الفكرة أربع خطوات لا تغادر Airtable: أرسِل معلمات reCAPTCHA v2 إلى in.php، ثم استطلِع النتيجة على res.php، واستقبِل الرمز المحلول، وأخيرًا اكتبه في السجل ليتابع بقية سير العمل.


ما تحتاجه قبل البدء

قبل ربط الطرفين، جهّز ثلاثة عناصر أساسية:

  • حساب Airtable مع صلاحية إنشاء الجداول والأتمتة داخل القاعدة.
  • مفتاح الـ API من حسابك على CaptchaAI للمصادقة على الطلبات.
  • قيمة sitekey الخاصة بـ reCAPTCHA v2 في الصفحة المستهدفة.

أين يتفوّق تشغيل الحل من داخل قاعدة Airtable؟

يصبح هذا النمط الخيار الأوفر حين يكون Airtable مركز عملياتك أصلًا، فلا داعي لإضافة خدمة خلفية لمجرد اجتياز اختبار تحقّق. أمثلة قريبة من السوق العربي:

  • فريق نمو في دبي يرصد أسعار المنافسين على صفحات محمية قبل إدخالها في تحليلاته.
  • متجر إلكتروني في القاهرة يجمّع روابط موردين تمهيدًا لاستخراج تفاصيل منتجاتها.
  • قسم تسويق في الرياض يتابع صفحات هبوط تفرض اجتياز reCAPTCHA قبل عرض محتواها.

في هذه الحالات يكفي عمود واحد داخل الجدول ليتحوّل إلى قائمة انتظار للحل، وتجري الدورة على أربع مراحل:

  1. تضيف رابطًا جديدًا إلى الجدول.
  2. تلتقطه الأتمتة فترسل معلماته إلى الخدمة.
  3. تستقبل الرمز المحلول من CaptchaAI.
  4. يُكتب الرمز في السجل نفسه ليستهلكه بقية المسار.

الحقول التي يقوم عليها جدول «CAPTCHA Tasks»

كل شيء يبدأ من جدول واحد مضبوط الأعمدة. أنشئ جدولًا باسم CAPTCHA Tasks يضم الحقول التالية، فهي مدخلات السكربت وأداة تتبّع حالة كل مهمة على حدة:

اسم الحقل النوع الغرض
URL URL عنوان الصفحة المستهدفة
Sitekey نص من سطر واحد مفتاح موقع reCAPTCHA
Status اختيار مفرد pending أو solving أو solved أو failed
Token نص طويل الرمز الناتج بعد الحل
Solved At تاريخ/وقت الطابع الزمني للحل
Error نص من سطر واحد رسالة الخطأ عند الفشل

أبقِ أسماء الحقول بالإنجليزية كما هي؛ فالسكربت يشير إليها بالاسم حرفيًا، وأي فرق في التسمية يوقف عملية التحديث.


الخطوة 1 — إنشاء الأتمتة وضبط المحفّز

  1. افتح تبويب Automations في قاعدتك.
  2. اختر Create automation.
  3. امنح الأتمتة اسمًا واضحًا، مثل «حل CAPTCHA عند كل سجل جديد».

المحفّز

اختر When record matches conditions، واضبطه على القيم التالية:

  • الجدول: CAPTCHA Tasks
  • الشرط: قيمة Status تساوي pending

بذلك يُطلَق المحفّز كلما صارت حالة السجل pending، فيغطّي السجلات الجديدة وعمليات إعادة الحل معًا دون إعداد إضافي.


الخطوة 2 — لصق سكربت الحل الفردي

أضف إجراء Run a script والصق الكود التالي. يرفع السكربت الحالة إلى solving، ثم يرسل الطلب إلى نقطة النهاية in.php، وينتظر النتيجة عبر الاستطلاع الدوري على res.php، ويكتب الرمز المحلول في السجل عند نجاحه:

// Airtable Automation Script — Solve CAPTCHA via CaptchaAI

// Input configuration (set in the left panel):
// - recordId: Record ID from trigger
// - sitekey: Sitekey field from trigger
// - pageurl: URL field from trigger
const config = input.config();
const recordId = config.recordId;
const sitekey = config.sitekey;
const pageurl = config.pageurl;

const API_KEY = 'YOUR_API_KEY'; // Use input.config() for security

// Update status to "solving"
const table = base.getTable('CAPTCHA Tasks');
await table.updateRecordAsync(recordId, {
  'Status': { name: 'solving' },
});

try {
  // Step 1: Submit task to CaptchaAI
  const submitUrl = `https://ocr.captchaai.com/in.php?key=${API_KEY}&method=userrecaptcha&googlekey=${encodeURIComponent(sitekey)}&pageurl=${encodeURIComponent(pageurl)}&json=1`;

  const submitResponse = await fetch(submitUrl);
  const submitResult = await submitResponse.json();

  if (submitResult.status !== 1) {
    throw new Error(`Submit failed: ${submitResult.request}`);
  }

  const taskId = submitResult.request;
  console.log(`Task submitted: ${taskId}`);

  // Step 2: Poll for result (wait 15 seconds first)
  await new Promise(resolve => setTimeout(resolve, 15000));

  let token = null;
  for (let i = 0; i < 20; i++) {
    const pollUrl = `https://ocr.captchaai.com/res.php?key=${API_KEY}&action=get&id=${taskId}&json=1`;
    const pollResponse = await fetch(pollUrl);
    const pollResult = await pollResponse.json();

    if (pollResult.status === 1) {
      token = pollResult.request;
      break;
    }

    if (pollResult.request !== 'CAPCHA_NOT_READY') {
      throw new Error(`Solve failed: ${pollResult.request}`);
    }

    await new Promise(resolve => setTimeout(resolve, 5000));
  }

  if (!token) {
    throw new Error('Polling timeout — CAPTCHA not solved in time');
  }

  // Step 3: Update record with solved token
  await table.updateRecordAsync(recordId, {
    'Status': { name: 'solved' },
    'Token': token,
    'Solved At': new Date().toISOString(),
    'Error': '',
  });

  console.log(`CAPTCHA solved for record ${recordId}`);

} catch (error) {
  // Update record with error
  await table.updateRecordAsync(recordId, {
    'Status': { name: 'failed' },
    'Error': error.message,
  });
  console.error(`Failed: ${error.message}`);
}

نصيحة أمان: لا تكتب مفتاح الـ API صراحةً في الكود؛ مرّره كمتغيّر إدخال سرّي عبر input.config() كي لا يظهر في سجلّ تشغيل الأتمتة.

ربط مدخلات السكربت

في اللوحة اليسرى لإجراء البرمجة النصية، اربط متغيرات الإدخال بحقول خطوة المحفّز:

  • recordId — معرّف السجل الآتي من المحفّز.
  • sitekey — حقل Sitekey من المحفّز.
  • pageurl — حقل URL من المحفّز.

هذا الربط هو ما ينقل بيانات كل سجل إلى الكود؛ وأي متغيّر غير مربوط يصل فارغًا فيُفشِل الإرسال.

الخطوة 3 — حلّ دفعة كاملة عبر Scripting extension

حين تتراكم عشرات السجلات مرة واحدة، يصبح تشغيل أتمتة مستقلة لكل سجل غير عملي. استعِن بدلًا من ذلك بـ Scripting extension المتاح في لوحة التطبيقات؛ فهو يمرّ على كل سجل بحالة pending ويحلّه تباعًا في تشغيل واحد:

بخلاف الأتمتة التي تنطلق تلقائيًا لكل سجل، يُشغَّل هذا الملحق يدويًا وقتما تشاء، فهو الأنسب لتصريف قائمة متراكمة دفعة واحدة.

// Batch CAPTCHA Solver — Airtable Scripting Extension
const API_KEY = 'YOUR_API_KEY';
const table = base.getTable('CAPTCHA Tasks');

// Get all pending records
const query = await table.selectRecordsAsync({
  fields: ['URL', 'Sitekey', 'Status'],
});

const pendingRecords = query.records.filter(
  r => r.getCellValueAsString('Status') === 'pending'
);

output.text(`Found ${pendingRecords.length} pending CAPTCHAs`);

for (const record of pendingRecords) {
  const sitekey = record.getCellValueAsString('Sitekey');
  const pageurl = record.getCellValueAsString('URL');

  if (!sitekey || !pageurl) {
    output.text(`Skipping ${record.id} — missing sitekey or URL`);
    continue;
  }

  output.text(`Solving for: ${pageurl}`);

  await table.updateRecordAsync(record.id, {
    'Status': { name: 'solving' },
  });

  try {
    // Submit
    const submitResp = await fetch(
      `https://ocr.captchaai.com/in.php?key=${API_KEY}&method=userrecaptcha&googlekey=${encodeURIComponent(sitekey)}&pageurl=${encodeURIComponent(pageurl)}&json=1`
    );
    const submitData = await submitResp.json();

    if (submitData.status !== 1) throw new Error(submitData.request);

    // Poll
    await new Promise(r => setTimeout(r, 15000));
    let token = null;

    for (let i = 0; i < 20; i++) {
      const pollResp = await fetch(
        `https://ocr.captchaai.com/res.php?key=${API_KEY}&action=get&id=${submitData.request}&json=1`
      );
      const pollData = await pollResp.json();

      if (pollData.status === 1) { token = pollData.request; break; }
      if (pollData.request !== 'CAPCHA_NOT_READY') throw new Error(pollData.request);
      await new Promise(r => setTimeout(r, 5000));
    }

    if (!token) throw new Error('Timeout');

    await table.updateRecordAsync(record.id, {
      'Status': { name: 'solved' },
      'Token': token,
      'Solved At': new Date().toISOString(),
    });
    output.text(`✓ Solved: ${pageurl}`);

  } catch (e) {
    await table.updateRecordAsync(record.id, {
      'Status': { name: 'failed' },
      'Error': e.message,
    });
    output.text(`✗ Failed: ${e.message}`);
  }
}

output.text('Batch processing complete');

علاج الأعطال الأكثر شيوعًا

قبل إطلاق الأتمتة على بيانات حقيقية، راجع هذه الأعطال المتكررة وطريقة معالجة كلٍّ منها:

المشكلة السبب المحتمل الحل
الأتمتة لا تعمل السجل لا يطابق شرط pending تمامًا تأكد من أن قيمة حقل Status مطابقة حرفيًا للشرط المضبوط
fetch is not defined بعض سياقات Scripting في Airtable تستخدم remoteFetchAsync استبدل fetch بـ remoteFetchAsync
انتهاء مهلة السكربت سكربتات الأتمتة محدودة بمهلة 30 ثانية قلّل عدد دورات الاستطلاع الدوري وزد مدة الانتظار الأولى
فشل تحديث السجل اسم الحقل في updateRecordAsync لا يطابق الجدول طابق أسماء الحقول بين الكود وهيكل الجدول
كشف مفتاح الـ API داخل الكود كتابة المفتاح صراحةً في السكربت استخدم input.config() مع متغيّر إدخال سرّي

كم تكلّف العملية وكم Thread تحتاج؟

يقوم تسعير CaptchaAI على عدد الـ Threads المتزامنة لا على عدد عمليات الحل، وكل خطة تمنحك عمليات حل غير محدودة طوال الشهر. المعيار الحاسم إذن هو كم طلبًا تريد تشغيله في آنٍ واحد، لا كم عملية حل تجريها شهريًا:

  • جدول خفيف يعالج بضع عشرات من الروابط يوميًا: تكفيه خطة BASIC ($15 شهريًا، 5 Threads).
  • دفعات كبيرة متوازية عبر Scripting extension: تمنحك خطة ADVANCE ($90 شهريًا، 50 Thread) متّسعًا لتشغيل عمليات حل عديدة معًا دون اختناق.

للاطلاع على بقية الخطط واختيار ما يناسب حجمك، راجع صفحة الأسعار الرسمية.

أسئلة شائعة

إجابات مختصرة عن أكثر ما يتكرر عند تشغيل هذا التكامل داخل Airtable.

هل يعمل هذا التكامل على خطة Airtable المجانية؟

المكوّنان الأساسيان — Automations وScripting extension — متاحان في خطط Airtable بما فيها المجانية، لكن حصص تشغيل الأتمتة الشهرية تختلف بحسب الخطة. راجع صفحة خطط Airtable لمعرفة الحدود المحدّثة قبل الاعتماد على التكامل في أحجام كبيرة.

كيف أمنع حلّ السجل نفسه أكثر من مرة؟

يعتمد المنع على حقل Status عبر آليتين متكاملتين:

  • المحفّز يُطلَق عند القيمة pending فقط.
  • أول ما يفعله السكربت رفع الحالة إلى solving، فيخرج السجل فورًا من نطاق الشرط ولا يُعاد التقاطه.

ولإعادة الحل عمدًا، أعِد الحالة يدويًا إلى pending.

هل أستطيع استخدام الجدول نفسه لأنواع CAPTCHA غير reCAPTCHA v2؟

نعم. يكفي تعديل قيمة method والمعلمات المرافقة مع إبقاء بنية الجدول كما هي. فإلى جانب reCAPTCHA v2، تحل الخدمة أنواعًا أخرى، منها:

  • reCAPTCHA v3 وv2 Invisible وEnterprise.
  • Cloudflare Turnstile وCloudflare Challenge.
  • GeeTest v3 وصور OCR والشبكات الصورية.

ماذا لو انتهت صلاحية الرمز قبل استخدامه؟

رمز reCAPTCHA قصير العمر (نحو دقيقتين)، لذا صمّم سير العمل بحيث يُستهلَك الرمز المحفوظ بسرعة في الخطوة التالية. وإن انقضت مهلته قبل الاستخدام، أعِد حالة السجل إلى pending ليُحلّ من جديد ويُنتج رمزًا حديثًا.

الخطوات التالية

تابع من حيث انتهيت عبر هذه الأدلة العملية:

أدلة ذات صلة

التعليقات غير مفعّلة لهذا المقال.