پرش به محتویات

پیام‌رسان‌ها

سه پیام‌رسان، یک لایهٔ گفتگو. این صفحه دربارهٔ تفاوت‌هایشان است و اینکه چطور چهارمی را اضافه کنیم.

آداپتور چیست

یک مترجم. بار سرویس‌دهنده وارد، IncomingEvent خارج. OutgoingMessage وارد، فراخوان API سرویس‌دهنده خارج. همین.

آداپتور نه نشست دیتابیس دارد، نه سرویس، و نه هیچ تصوری از اینکه دسترسی چیست. test_the_adapter_never_reaches_the_database هر ماژول داخل adapters/ را تجزیه می‌کند و اگر یکی SQLAlchemy یا سرویسی وارد کند ساخت را می‌شکند — گراف واردات را می‌پیماید نه متن را، پس توضیحی که نام مدلی را ببرد ساخت را نمی‌شکند و واردات واقعی نمی‌تواند داخل یک رشته پنهان شود.

هر سه

تلگرام

پیاده‌سازی مرجع. هر چیزی که در Capabilities هست پشتیبانی می‌شود: دکمهٔ درون‌خطی، ویرایش پیام، حذف، بارگذاری و دریافت فایل، پیوند عمیق، وب‌هوک، پولینگ.

فقط Bot API رسمی. https://api.telegram.org/bot{token}/{method}.

بله

API ربات بله همان API ربات تلگرام است با میزبان دیگر — همان متدها، همان پاکت به‌روزرسانی، همان صفحه‌کلید درون‌خطی. پس BaleAdapter چهار خط پیکربندی روی BotApiAdapter است، یعنی همان گویش مشترک، و هر رفتاری را که تست‌های تلگرام از قبل پوشش می‌دهند به ارث می‌برد.

https://tapi.bale.ai/bot{token}/{method}، پیوند عمیق روی ble.ir.

هیچ وب‌هوکی را امضا نمی‌کند — امنیت را ببین.

فقط API رسمی استفاده می‌شود. بله وب‌کلاینتی هم دارد که می‌شود نقاط پایانی‌اش را رصد کرد، و ورود با حسابی که کد یک‌بارمصرف به شمارهٔ واقعی می‌فرستد. هیچ‌کدام جایشان در یک محصول نیست.

روبیکا

واقعاً متفاوت است، پس آداپتور واقعی است نه زیرکلاس. سه تفاوت همه‌چیز را می‌رانند:

پاکت. {"status": "OK", "data": {...}} — نه okی، نه resultی.

دکمه‌ها کال‌بک نیستند. فشار به‌صورت پیام تازهٔ معمولی می‌رسد که aux_data.button_id را حمل می‌کند. شناسهٔ کال‌بکی نیست، چیزی نمی‌چرخد، و answer_callback یک بی‌عمل مستندشده است که وجود دارد تا لایهٔ بالاتر بتواند صدایش بزند بی‌آنکه بپرسد با کدام سرویس‌دهنده حرف می‌زند.

صفحه‌کلیدها بار را در شناسه حمل می‌کنند. روبیکا هنگام فشار id را برمی‌گرداند، پس دادهٔ کال‌بک همان‌جا می‌رود — نه در فیلدی جدا.

همچنین: بارگذاری دو مرحله است (اول درخواست نشانی، بعد پست‌کردن بایت‌ها به آن)، صفحه‌بندی به‌جای آفست عددی از مکان‌نمای مبهم next_offset_id استفاده می‌کند، و پولینگ بلند وجود ندارد.

https://botapi.rubika.ir/v3/{token}/{method}.

ایتا

ساخته نشده. عمداً به تعویق افتاده — نقشهٔ راه را ببین. Provider.EITAA در شمارش هست تا هویت‌ها بتوانند به آن ارجاع بدهند، و درخواستش از اجراکننده موقع بالا آمدن با «آداپتوری نیست» شکست می‌خورد، به‌جای AttributeError سه صفحه بعد.

توانمندی‌ها

هر آداپتور اعلام می‌کند واقعاً چه می‌تواند. هر چیزی که نادرست باشد در رابط کاربری خاموش می‌شود، به‌جای اینکه امتحان شود و شکست بخورد.

تلگرام بله روبیکا
دکمهٔ درون‌خطی ✓ ✓ ✓ (به‌شکل inline_keypad)
ویرایش پیام ✓ ✓ ✓ (متن و صفحه‌کلید جدا)
پاسخ به کال‌بک ✓ ✓ — (چیزی برای پاسخ نیست)
امضای وب‌هوک ✓ — —
پنهان‌سازی متن ✓ — —
دکمهٔ رنگی (style) ✓ — —
پولینگ بلند ✓ ✓ — (صفحه‌بندی با مکان‌نما)

این دو تای آخر برای بله خاموش‌اند، هرچند botapi.py با او مشترک است، و این ناهم‌تقارنی تصمیم است نه از قلم افتادن. بله به گفتهٔ مستندات خودش همان Bot API تلگرام روی میزبان دیگری است، اما نقشهٔ راه ثبت کرده که نه بله و نه روبیکا در برابر API زندهٔ خودشان تست نشده‌اند، و دو حالت شکست هم‌تقارن نیستند: کلید ناشناسی در reply_markup که نادیده گرفته شود چیزی خرج نمی‌کند، اما همان کلید اگر رد شود کل پیام را خرج می‌کند، پس خواننده صفحه را از دست می‌دهد نه رنگ را. spoiler همین رسم را گذاشت و button_style از آن پیروی می‌کند. روشن کردن هر کدام یک کلمه در bale.py است، همان روزی که کسی زنده تستش کند.

روبیکا حالت دیگری است. صفحه‌کلیدش {id, type, button_text} است که هیچ جایی برای style ندارد، پس build_buttons این فیلد را می‌اندازد نه اینکه دکمه را رد کند — همان افت تدریجی که این لایه قولش را می‌دهد، نه یک رد.

Button.style هنگام ساخت دکمه در adapters/base.py در برابر BUTTON_STYLES اعتبارسنجی می‌شود، پس یک غلط تایپی در تست صفحه‌ها می‌شکند نه در لحظهٔ ارسال؛ style ناشناس reply_markup خراب است و تلگرام کل پیام را رد می‌کند. هنوز هیچ صفحه‌ای style نمی‌گذارد: توانمندی هست و چیزی با آن رنگی نشده.

مجموعهٔ انطباق

tests/test_adapters_bale_rubika.py با همان ادعاهایی تمام می‌شود که روی هر سه آداپتور اجرا می‌شوند:

  • یک Provider و Capabilities اعلام می‌کند
  • شناسهٔ پیامی برمی‌گرداند که بعداً بشود ویرایشش کرد — بدونش، تجربهٔ تک‌صفحه‌ای تا ابد پیام اضافه می‌کند
  • دکمه‌هایی را که به آن داده شده واقعاً می‌فرستد
  • به‌روزرسانی‌ای را که به آن ربطی ندارد نادیده می‌گیرد
  • پیوند عمیق بدون نام‌کاربری را رد می‌کند
  • سرویس‌دهندهٔ دور از دسترس را دور از دسترس گزارش می‌دهد، نه «به‌روزرسانی‌ای نیست»
  • رد را به‌عنوان رد گزارش می‌دهد
  • کل پروتکل را پیاده می‌کند

آخری بیشتر از آنچه به نظر می‌رسد مهم است. متد نبوده یعنی پکیدن، همان اولین باری که کاربری آن صفحه را باز می‌کند — در محیط عملیاتی، روی پیام‌رسانی که هیچ‌کس دستی امتحانش نکرده.

سه نتیجه، نه دو

fetch_updates یک UpdateBatch با سه حالت ممکن برمی‌گرداند:

batch.ok           # به‌روزرسانی رسید (شاید صفر تا)
batch.refused      # سرویس‌دهنده جواب داد، و گفت نه
batch.unreachable  # اصلاً چیزی جواب نداد

فروکاستن آن دو تای آخر به «به‌روزرسانی‌ای نیست» همان راهی است که ربات یک هفته بی‌صدا در برابر توکن باطل‌شده می‌چرخد. اجراکننده برای هرکدام جور دیگری عقب می‌نشیند: سرویس‌دهندهٔ دور از دسترس معمولاً لحظه‌ای است و زود دوباره تلاش می‌کند؛ رد یعنی توکن باطل‌شده یا انتظار اجباری، و سخت‌تر عقب می‌نشیند، چون کوبیدن هر دو را بدتر می‌کند.

افزودن یک پیام‌رسان

اگر API‌اش تلگرام‌شکل است، چهار خط است:

class NewAdapter(BotApiAdapter):
    provider = Provider.NEW
    api_root = "https://api.example.com"
    deep_link_template = "https://example.com/{username}?start={payload}"
    secret_header = "X-Example-Secret"      # یا "" اگر چیزی را امضا نمی‌کند

اگر نیست، آداپتور واقعی بنویس. فهرست کارها:

  1. مقدار را به Provider در modules/identity/models.py اضافه کن.
  2. اگر آدم می‌تواند وصلش کند، به MESSENGERS اضافه‌اش کن.
  3. adapters/<name>.py را بنویس و پروتکل داخل adapters/base.py را پیاده کن.
  4. Capabilities صادق اعلام کن. نادرست یعنی امکانی خاموش، نه امکانی که شکست می‌خورد.
  5. در ADAPTERS داخل apps/bot/runner.py ثبتش کن.
  6. <NAME>_BOT_TOKEN و <NAME>_BOT_USERNAME را به shared/settings.py اضافه کن.
  7. به ADAPTERS در تست انطباق اضافه‌اش کن. خود مجموعه می‌گوید چه کم داری.
  8. مهاجرت فقط اگر ستونی لازم دارد. معمولاً ندارد.

بعد برای هر پیام‌رسان یک پروسه اجرا کن: یونیت دوم با فایل محیط خودش، روی همان دیتابیس.

آنچه بیرون از خط است

نه نقطهٔ پایانی مهندسی‌معکوس‌شدهٔ وب‌کلاینت. نه ورود به‌عنوان انسان با کد یک‌بارمصرفی که به شمارهٔ واقعی می‌رود. نه کتابخانهٔ کلاینت غیررسمی به‌عنوان وابستگی هسته.

ربات به‌عنوان ربات پست می‌گذارد. هر چیز دیگری یک تغییر شرایط استفاده فاصله دارد تا همهٔ استقرارها را یک‌جا بخواباند، و برای صرفه‌جویی در نوشتن یک آداپتور حساب یک آدم واقعی را به خطر می‌اندازد.