الشروحات المعمقة

آلية Callback في reCAPTCHA v2: كيف تعمل وكيف تستدعيها بعد الحل

كتبتَ الرمز المحلول داخل حقل g-recaptcha-response، ومع ذلك بقي زر الإرسال معطّلاً. المشكلة ليست في الرمز، بل في دالة Callback التي لم يستدعِها أحد: حين يجتاز مستخدم حقيقي التحدي، تستدعي مكتبة Google دالة JavaScript يعرّفها الموقع، وهي التي تفعّل الزر أو ترسل طلب AJAX أو تُكمل التحقق.

عندما تحصل على الرمز من CaptchaAI عبر الـ API، فأنت تتولّى دور مكتبة Google هنا. المسار الكامل أربع خطوات.

  1. اقرأ data-sitekey واسم دالة Callback من مصدر الصفحة.
  2. أرسل المهمة إلى CaptchaAI واستطلع النتيجة حتى يصلك الرمز.
  3. اكتب الرمز في textarea المسمّى g-recaptcha-response.
  4. استدعِ دالة Callback ومرّر الرمز إليها كوسيط وحيد.

الخطوات الثلاث الأولى متكررة في كل تكامل، والرابعة هي التي تُنسى.


ما الذي تفعله دالة Callback داخل أداة reCAPTCHA v2

يعرّف الموقع اسم الدالة في سمة data-callback على عنصر الأداة نفسه:

<div class="g-recaptcha"
     data-sitekey="6Le-SITEKEY"
     data-callback="onCaptchaSuccess"
     data-expired-callback="onCaptchaExpired">
</div>

<script>
function onCaptchaSuccess(token) {
  document.getElementById('submit-btn').disabled = false;
  document.getElementById('captcha-token').value = token;
}
</script>

عند نجاح التحدي تستدعي مكتبة Google الدالة onCaptchaSuccess ومعها الرمز كوسيط وحيد. ما يجري داخل الدالة يختلف من موقع لآخر: تفعيل زر، ملء حقل مخفي، أو إطلاق طلب AJAX يُكمل تسجيل الدخول. لذلك لا توجد «خطوة إرسال» موحّدة تصلح لكل المواقع؛ القاعدة الثابتة أن الرمز وحده لا يكفي ما لم يمرّ عبر هذه الدالة. ولاحظ السمة الثانية data-expired-callback؛ سنعود إليها لاحقاً.


ثلاث طرق للعثور على اسم دالة Callback

الترتيب التالي مقصود: ابدأ بالأبسط، ولا تنتقل إلى التالية إلا إذا عادت فارغة.

الطريقة الأولى: اقرأ سمة data-callback مباشرة

أسرع فحص، ويكفي في أغلب النماذج. افتح وحدة تحكم المتصفح ونفّذ:

// In browser console
const widget = document.querySelector('.g-recaptcha');
const callbackName = widget?.getAttribute('data-callback');
console.log('Callback:', callbackName);

إذا طبع لك اسماً، فهذا الاسم موجود على الأرجح في النطاق العام window ويمكن استدعاؤه مباشرة بعد الحقن.

الطريقة الثانية: افحص خيارات grecaptcha.render

بعض المواقع لا تكتب السمة في HTML، بل تُنشئ الأداة عبر grecaptcha.render() وتمرّر الدالة داخل كائن الخيارات. ابحث في سكربتات الصفحة عن هذا النمط:

// Search page source for grecaptcha.render
document.querySelectorAll('script:not([src])').forEach(s => {
  if (s.textContent.includes('grecaptcha.render')) {
    console.log(s.textContent.match(/callback\s*:\s*(\w+)/)?.[1]);
  }
});

الطريقة الثالثة: اعترض التسجيل قبل تحميل الأداة

حين تكون الدالة مجهولة الاسم أو داخل closure، لن تفيدك القراءة من DOM. التقط الخيارات لحظة تسجيلها: أضف المقتطف التالي في DevTools ضمن لوحة Sources → Snippets، ثم أعد تحميل الصفحة.

const origRender = grecaptcha.render;
grecaptcha.render = function(container, params) {
  console.log('Render callback:', params.callback);
  console.log('Expired callback:', params['expired-callback']);
  return origRender.apply(this, arguments);
};

وهذه خلاصة الطرق الثلاث:

الطريقة متى تنفع ما تحصل عليه
قراءة data-callback أداة معرّفة داخل HTML اسم دالة عام جاهز للاستدعاء
فحص grecaptcha.render أداة تُنشأ من JavaScript اسم الدالة أو مرجعها داخل الخيارات
اعتراض grecaptcha.render دوال مجهولة أو داخل closure مرجع الدالة نفسه أثناء التشغيل

استدعاء Callback بعد حقن الرمز في Python وSelenium

المثال التالي يجمع المسار كاملاً: قراءة data-sitekey واسم الدالة، إرسال المهمة إلى in.php، الاستطلاع الدوري من res.php، ثم الحقن والاستدعاء. يحلّ CaptchaAI اختبار reCAPTCHA v2 عادةً في أقل من 60 ثانية، لذا فإن فاصلاً من خمس ثوانٍ مع 24 محاولة يعطي هامشاً مريحاً دون إغراق نقطة النهاية.

import requests
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://example.com/login")

# Extract sitekey and callback
sitekey = driver.find_element(
    By.CSS_SELECTOR, ".g-recaptcha"
).get_attribute("data-sitekey")

callback = driver.find_element(
    By.CSS_SELECTOR, ".g-recaptcha"
).get_attribute("data-callback")

# Solve with CaptchaAI
resp = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": driver.current_url,
    "json": "1",
}).json()
task_id = resp["request"]

token = None
for _ in range(24):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "get", "id": task_id, "json": "1"
    }).json()
    if result["status"] == 1:
        token = result["request"]
        break

# Inject token into textarea
driver.execute_script("""
    document.querySelector('textarea[name="g-recaptcha-response"]').value = arguments[0];
""", token)

# Trigger the callback
if callback:
    driver.execute_script(f"window['{callback}'](arguments[0]);", token)
    print(f"Triggered callback: {callback}")
else:
    # Fallback: try ___grecaptcha_cfg
    driver.execute_script("""
        try {
            var widgetId = Object.keys(___grecaptcha_cfg.clients)[0];
            var callback = ___grecaptcha_cfg.clients[widgetId].aa.l.callback;
            if (typeof callback === 'function') callback(arguments[0]);
        } catch(e) {}
    """, token)
    print("Triggered callback via ___grecaptcha_cfg")

إذا كانت السمة فارغة، ينتقل السكربت إلى المسار الاحتياطي ___grecaptcha_cfg — كائن داخلي تحفظ فيه مكتبة Google إعدادات كل أداة. هذا المسار أقل استقراراً لأن أسماء حقوله تتغيّر مع تحديثات المكتبة، ولهذا يبقى داخل try.

النسخة نفسها في Puppeteer

المبدأ واحد في Node.js، مع فارق عملي: بعض المواقع تُخفي textarea الخاص بالرمز عبر CSS، وإعادة إظهاره أثناء التصحيح تؤكد لك أن القيمة كُتبت فعلاً قبل استدعاء الدالة.

const puppeteer = require('puppeteer');

// After solving and getting the token...
await page.evaluate((token, callbackName) => {
  // Set textarea value
  const textarea = document.querySelector(
    'textarea[name="g-recaptcha-response"]'
  );
  textarea.value = token;
  textarea.style.display = 'block'; // sometimes hidden

  // Trigger callback
  if (callbackName && typeof window[callbackName] === 'function') {
    window[callbackName](token);
    console.log(`Called ${callbackName}()`);
  } else {
    // Fallback: search grecaptcha config
    try {
      const clients = ___grecaptcha_cfg.clients;
      const widgetId = Object.keys(clients)[0];
      const cb = clients[widgetId]?.aa?.l?.callback;
      if (typeof cb === 'function') cb(token);
    } catch (e) {}
  }
}, token, callbackName);

حالة عملية: بوابة حجز مواعيد تحت اختبار الانحدار

فريق هندسة في منصة حجز مواعيد بالرياض يشغّل اختبارات انحدار ليلية على بيئة التجهيز الخاصة به، وصفحة تسجيل الدخول محمية بـ reCAPTCHA v2 مع دالة اسمها enableLogin تفعّل الزر ثم ترسل طلب AJAX. أول تشغيل فشل بالكامل رغم أن الرموز القادمة من CaptchaAI كانت صحيحة: السكربت يكتب الرمز في textarea ثم ينقر الزر مباشرة، والزر لا يزال معطّلاً فيضيع النقر. سطر واحد يستدعي enableLogin(token) بعد الحقن أعاد مجموعة الاختبارات إلى النجاح الكامل.

الجانب الثاني يتعلق بالحجم لا بالشيفرة. الفريق يشغّل نحو 40 سيناريو متوازياً، وكل سيناريو يحتاج عملية حل واحدة. ولأن خطط CaptchaAI مبنية على عدد الـ threads المتزامنة لا على عدد عمليات الحل، فإن خطة ADVANCE بسعر $90 شهرياً مع 50 thread تستوعب الموجة كاملة، بينما يكفي فريقاً بخمسة سيناريوهات ليلية أن يبدأ من BASIC بسعر $15 شهرياً مع 5 threads — والأسعار بالدولار الأمريكي.


مواقع بلا data-callback: التعامل مع grecaptcha.getResponse

ليست كل المواقع تعتمد على Callback. بعضها ينتظر إرسال النموذج ثم يستدعي grecaptcha.getResponse() ويقارن الناتج بمحتوى الحقل. هنا لا توجد دالة تستدعيها، لكن getResponse() قد تُعيد سلسلة فارغة لأن الأداة لم تُكمل دورتها. الحل أن تكتب الرمز في الحقل وتُعيد تعريف الدالة لتُعيد الرمز ذاته، ثم ترسل النموذج كالمعتاد:

driver.execute_script("""
    const token = arguments[0];
    document.querySelector('textarea[name="g-recaptcha-response"]').value = token;
    // Override getResponse to return the token
    if (typeof grecaptcha !== 'undefined') {
        grecaptcha.getResponse = function() { return token; };
    }
""", token)

# Then submit the form normally
driver.find_element(By.CSS_SELECTOR, "form").submit()

وإذا كان التحقق من الرمز يجري على جانب الخادم فقط، فغالباً يكفي ملء الحقل وإرسال النموذج دون أي تعديل على grecaptcha.


Callback انتهاء الصلاحية وسباق التوقيت

السمة data-expired-callback تشير إلى دالة تستدعيها مكتبة Google عند انتهاء صلاحية الرمز، وعملها المعتاد أن تُعيد قفل الزر وتمسح الحقل. صلاحية رمز reCAPTCHA v2 قصيرة بطبيعتها، لذا اجعل الحل أقرب ما يمكن من لحظة الإرسال لا في أول السكربت. وتحقّق أولاً من وجود دالة انتهاء صلاحية حتى تعرف ما الذي سيحدث إن تأخّرت:

// Check for expired callback
const expiredCallback = document.querySelector('.g-recaptcha')
  ?.getAttribute('data-expired-callback');
console.log('Expired callback:', expiredCallback);

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


قائمة تحقق سريعة قبل التشغيل

  • استخرج اسم الدالة من DOM في كل جلسة بدل تثبيته في الشيفرة.
  • مرّر الرمز كوسيط للدالة؛ استدعاؤها فارغة يفشل في أغلب النماذج.
  • ضع المسار الاحتياطي ___grecaptcha_cfg داخل try دائماً.
  • افحص data-expired-callback وقصّر الزمن بين الحل والإرسال.

أعطال شائعة وكيف تقرؤها

العَرَض السبب المرجّح المعالجة
النموذج ما زال معطّلاً بعد الحقن لم تُستدعَ دالة Callback استخرج اسم الدالة واستدعِها ومعها الرمز
ReferenceError: function not defined الدالة داخل closure ولا تظهر في window استخدم المسار الاحتياطي ___grecaptcha_cfg
الرمز مكتوب وطلب AJAX لا يُرسَل الدالة تُطلق AJAX ولا ترسل النموذج اقرأ محتوى الدالة وحدّد ما تفعله
الحقل ممتلئ وgetResponse() فارغة الأداة لم تُكمل دورتها أعد تعريف getResponse لتُعيد الرمز

أسئلة شائعة

لماذا يبقى زر الإرسال معطّلاً رغم أن الرمز ظاهر في الحقل؟

لأن تفعيل الزر مسؤولية دالة Callback لا مسؤولية الحقل. الحقل يخزّن القيمة التي ستصل إلى الخادم، بينما منطق الواجهة — تفعيل الزر، إخفاء رسالة الخطأ، إرسال AJAX — يعيش داخل الدالة. استدعِ الدالة ومرّر الرمز إليها وسيتغيّر حال الزر فوراً.

كيف أستدعي دالة معرّفة داخل closure ولا تظهر في window؟

لن تجدها بالاسم، فاسلك أحد مسارين: اعترض grecaptcha.render قبل تحميل الأداة والتقط مرجع الدالة من كائن الخيارات، أو اقرأ المرجع من ___grecaptcha_cfg.clients أثناء التشغيل. كلاهما يحتاج معالجة أخطاء لأن البنية الداخلية غير موثّقة.

كم أخصّص من الوقت لكل عملية حل؟

يحلّ CaptchaAI اختبار reCAPTCHA v2 عادةً في أقل من 60 ثانية، وهو أبطأ الأنواع لأن التحدي بصري. اجعل الاستطلاع بفاصل خمس ثوانٍ ومهلة إجمالية قريبة من الدقيقتين، ولا تبدأ الحل إلا حين تصبح خطوة الإرسال هي التالية مباشرة.

هل تختلف الآلية في reCAPTCHA v2 Invisible؟

المبدأ ذاته مع فارق في التوقيت: النسخة غير المرئية تُشغَّل عبر grecaptcha.execute() استجابةً لحدث في الصفحة، ثم تستدعي Callback نفسها عند النجاح. تبقى الخطوة الأخيرة — حقن الرمز ثم استدعاء الدالة — كما هي.


ابدأ حلّ reCAPTCHA v2 مع CaptchaAI

احصل على مفتاح الـ API من captchaai.com، ثم شغّل مثال Selenium أعلاه بعد استبدال YOUR_API_KEY بمفتاحك.


أدلة ذات صلة

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