وثائق المطوّرين
العربية

تطوير البوتات

البوتات الصوتية

عندما يتصل شخص ببوت من التطبيق يرد عليه نموذج صوتي بالذكاء الاصطناعي. كيفية التفعيل، والتعليمات والصوت، وإشعارات المكالمات، والأدوات، وخوادم MCP، ومفتاح المالك، ونص المحادثة، ووضع الصوت الخام، وApple Watch والاحتفاظ بالمحادثة — دليل لمالكي البوتات.

البوت الصوتي بوت يستطيع الناس الاتصال به هاتفياً من التطبيق. ومن يرد على المكالمة ليس شيفرة مالك البوت بل نموذج صوتي بالذكاء الاصطناعي من Pabal، ويحدد مالك البوت عبر Bot API التعليمات والتحية والصوت واللغة والحدود التي يتبعها ذلك النموذج. وفي كل مرة تبدأ مكالمة أو تنتهي يصلك التحديث voice_call، ويمكن للنموذج أثناء المكالمة أن يستدعي شيفرة البوت (الأدوات) ليبحث عن طلب أو يتحقق من حجز. وهناك أيضاً وضع الصوت الخام الذي يستلم فيه خادمك الصوت مباشرة بدلاً من نموذج الذكاء الاصطناعي. وإن كانت هذه أول مرة تنشئ فيها بوتاً فراجع أولاً إنشاء بوت.

الوضع الحالي

لا تعمل البوتات الصوتية إلا على خادم فُعّلت فيه، وهي معطّلة على pabal.me وقت تعديل هذا المستند (2026-10-02) — وتُختبر على خادم التطوير

لا يمكن الاتصال ببوت صوتي إلا على خادم تتحقق فيه الشروط أدناه كلها. والطرق موجودة حتى على الخوادم التي عُطّل فيها الصوت، فيمكنك قراءة الإعدادات. لكن قبل أن يُفعَّل الصوت على هذا الخادم ويسمح المشغّل لذلك البوت، لا يُحفظ أي إعداد سوى التعطيل (تعطيل الصوت · تعطيل نص المحادثة · تعطيل وضع الصوت الخام · حذف مفتاح المالك · حذف خادم MCP) (403 VOICE_NOT_AVAILABLE). وهل يستطيع بوتك الآن استقبال المكالمات؟ يخبرك بذلك available وunavailable_reason في getVoiceCallSettings.

الميزةكي تعمل (كل ما يلي)pabal.me الآن
مكالمات البوتات الصوتيةبوابة صوت وترحيل مكالمات (TURN) متصلان بالخادم · قسم مكالمات البوتات (bot-calls) منشور في سياسة الخصوصية · المشغّل سمح لذلك البوت · قبل الإطلاق العام لا يتصل إلا المُختبِرون الذين حدّدهم المشغّل · تطبيق المتصل بناء يتضمن نافذة التأكيد (سطح المكتب pabal-bc2 — ويُقبل pabal-bc1 أيضاً ما دامت نافذة التأكيد بالكورية والإنجليزية فقط، وAndroid البناء 62151، وiPhone البناء 5 فأعلى، وتطبيق Apple Watch البناء 1 فأعلى) — ولم تُوزَّع هذه البناءات بعدمعطّل
الاتصال من Apple Watchمكالمات البوتات الصوتية + واجهة الساعة البرمجية في الخادم + نقطة دخول صوت الساعة (pabal-voice-edge) + بوابة تستقبل المكالمات المباشرة (direct) · بناء تطبيق الساعة لا يقل عن TELEGRAM_VOICE_MIN_BUILD_WATCHOS · ولا يصح ذلك بعدُ للبوتات التي تستخدم الأدوات · خوادم MCP · مفتاح المالك · وضع الصوت الخام · نص المحادثة لمالك البوتمعطّل — يُختبر على خادم التطوير
الاحتفاظ بالمحادثة (نسخة المتصل)مكالمات البوتات الصوتية + قسم bot-calls-keep-text في السياسة + بوابة تعلن keep_text (0.9.0) + بناء تطبيق فيه خانة الاختيار · المراجعة القانونية لنصوص نافذة التأكيدمعطّل — قبل المراجعة القانونية
المكالمات من البوتاتنُشر قسم bot-calls-outbound في سياسة الخصوصية وقسم voice-bot-owners-outbound في شروط الاستخدام (وقسم voice-bot-owners أيضاً، ليوافق المالكون على النسخة 4 من الشروط) · المفتاح العام للمشغّل بعد المراجعة القانونية · يفعّلها مالك البوت في @BotFather (الشروط، النسخة 4، الأغراض) · مراجعة المشغّل للبوت · إذن المتلقّي (إصدار من التطبيق فيه نافذة الإذن) · بوابة لديها تسجيلات معتمدة لإعلانات المكالمات الصادرة · لغة البوت الكورية أو الإنجليزيةمعطّلة — بانتظار المراجعة القانونية
لغات البوت غير الكورية والإنجليزية (7 لغات)مكالمات البوتات الصوتية + بوابة تُجري المكالمات بلغة ذلك البوت (منذ الإصدار 0.7.0 يُجريها باللغات التسع كلها) · المشغّل قرأ تعليمات ذلك البوت وتحيته وسمح له (حتى على خادم يستقبل كل البوتات)معطّل — لم يُنشر بعد
نافذة التأكيد والإعلانات الصوتية بغير الكورية والإنجليزية (8 لغات)تُضمَّن كل لغة في إصدارات التطبيق وفي صورة البوابة بعد أن تجتاز المراجعة القانونية وتدقيق متحدث أصلي. وقبل ذلك يرى المتصل نافذة التأكيد بالإنجليزية ويسمع الإعلانات بالإنجليزية حتى لو كان يستخدم التطبيق بتلك اللغةمسودة — لم تُراجَع بعد
شروط مالكي البوتات (النسخة 4)قسم مالكي البوتات (voice-bot-owners) منشور في شروط الاستخدام. ونص المحادثة ومفتاح المالك ووضع الصوت الخام وخوادم MCP والمكالمات من البوتات كلها تتطلب هذه الشروط أولاًلا يمكن الموافقة
الأدواتمكالمات البوتات الصوتية + بوابة تقبل الأدواتمعطّل
خوادم MCPمكوّنات MCP · عنوان خروج مخصّص (شرط تشغيلي — لا تتحقق منه الشيفرة) · قسم bot-calls-mcp في السياسة وقسم مالكي البوتات في الشروط · موافقة مالك البوت على شروط مالكي البوتات · إصدار تطبيق يعرض جهة استلام MCP الخاصة بالبوت في نافذة التأكيد (وقبل ذلك، حتى لو سجّلت، لا تحمل أي مكالمة أدوات MCP)معطّل
مفتاح النموذج الخاص بالمالكمفتاح رئيسي على الخادم · يفعّله المشغّل بعد أن يُصدر التطبيق نافذة تأكيد (النسخة 2) تعرض جهة استلام البوت ذي مفتاح المالك · شروط مالكي البوتات · بوابة تشغّل مفاتيح المالكين (الافتراضي مفاتيح Google فقط)معطّل — لا يقبل المفاتيح
نص المحادثةيسمح به المشغّل في صفحة الإدارة بعد مراجعة قانونية (معطّل افتراضياً) · شروط مالكي البوتات · مفتاح التشغيل لدى مالك البوت · بوابة لديها صوت إعلان نص المحادثة · مكالمة مُدارة (managed)معطّل
وضع الصوت الخاممفتاح رئيسي على الخادم · قسم bot-calls-stream في السياسة وقسم مالكي البوتات في الشروط · بوابة تستقبل مكالمات الصوت الخام · إصدار تطبيق يتضمن نافذة تأكيد الصوت الخام (غير موجود بعد)معطّل
  • يجب أن يكون المتصل (من يجري المكالمة) في التاسعة عشرة أو أكثر، وأن يرى قبل الاتصال نافذة التأكيد «An AI answers this call» (يرد الذكاء الاصطناعي على هذه المكالمة)، وعند الاتصال يسمع أولاً إعلاناً صوتياً بأنه ذكاء اصطناعي (ما يراه المتصل). أما التطبيقات التي لا تتضمن نافذة التأكيد (ومنها تطبيق Telegram الأصلي والعملاء مثل Telethon) فلا يظهر فيها زر الاتصال بالبوت.
  • صوت المكالمات المُدارة (managed) يذهب إلى Google — Gemini Live API في Vertex AI، بولاية آيوا الأمريكية (Google Cloud us-central1). لا يخزّن Pabal الصوت. ولا تحتفظ Google بالصوت والنص بعد توليد الرد، لكن المحتوى الذي تشتبه الفحوص الآلية في أنه إساءة استخدام قد يُسجَّل مدة تصل إلى 90 يوماً (وهذا ما تقوله نافذة التأكيد أيضاً). ومحرّك الإنتاج هو هذا وحده، ولا يُنقل الصوت خفيةً إلى مزوّد آخر (لا يوجد تبديل تلقائي).
  • بخلاف المكالمات بين الأشخاص، مكالمات البوتات ليست مشفّرة من طرف إلى طرف: تفك بوابة الصوت في Pabal تشفير الصوت وتسلّمه إلى نموذج الذكاء الاصطناعي. ونافذة التأكيد لدى المتصل تُعلمه بذلك.

هكذا تسير المكالمة الواحدة

المتصل ⁧Pabal (خادم المشغّل)⁩ ⁧خارج Pabal⁩ ⁧تطبيق Pabal⁩ ⁧نافذة التأكيد · ⁦19+⁩⁩ إصدار معدَّل pabal-server MTProto · Bot API قرار القبول · الحدود سجل المكالمات برنامج مالك البوت Bot API (HTTPS) شيفرتك · خادمك coturn ترحيل الصوت فقط pabal-voice ⁧إعلان AI · التعليمات⁩ لا يخزّن الصوت Google Vertex AI Gemini Live ⁧us-central1 (الولايات المتحدة)⁩ ⁧① اتصال⁩ ⁧② الرابط⁩ المكالمة · الإعدادات · الأدوات ⁧③ voice_call · استعلام الأداة⁩ ⁧الإعدادات · رد الأداة · endVoiceCall⁩ الصوت (عبر الترحيل فقط) ⁧④ الصوت ↔ رد AI⁩
المكالمة المُدارة: الإشارات في الأعلى، والصوت في الأسفل

شرح المخطط

  • ثلاث مناطق: تطبيق المتصل ← خادم مشغّل Pabal (خادم Pabal، وبوابة الصوت، وخادم الترحيل) ← خارج Pabal (نموذج الذكاء الاصطناعي من Google، وبرنامج مالك البوت). ولا ترى شيفرة مالك البوت بايتاً واحداً من الصوت.
  • ① اتصال: يُبلغ التطبيق الخادمَ بالموافقة التي أخذها من نافذة التأكيد ثم يتصل. ويتحقق الخادم من المتصل (الموافقة — تأكيده بنفسه أنه في التاسعة عشرة أو أكثر · إصدار التطبيق · المُختبِرون · الحدود) ومن البوت (السماح · الإعدادات · الحدود) معاً، ثم يقبل المكالمة أو ينهيها بإشارة انشغال.
  • ② الرابط: يسلّم الخادم المكالمة إلى بوابة الصوت ومعها تعليمات مالك البوت وتحيته وصوته ولغته وأدواته. وتضع البوابة دائماً قواعد Pabal الأساسية (مثل الإفصاح عن أنه ذكاء اصطناعي) أولاً، ثم التعليمات بعدها.
  • ③ Bot API: يضبط برنامج مالك البوت الإعدادات، ويستلم voice_call عند بدء المكالمة وانتهائها، ويستلم voice_call_tool_query حين يستدعي النموذج أداة فيرد عليها. والسهم الأزرق في ③ هو ما يرسله الخادم إلى البوت، والسهم الرمادي تحته هو ما يرسله البوت إلى الخادم.
  • ④ الصوت (الصف السفلي): لا يعرف التطبيق والبوابة عنوانَي بعضهما، ولا يتبادلان الصوت إلا عبر خادم الترحيل (coturn)، وتسلّم البوابة الصوت إلى نموذج Google وتستلم الرد. والخانة الحمراء هي حيث يخرج الصوت من Pabal.
  • في وضع الصوت الخام يحل خادم مالك البوت محل Google في ④ (وضع الصوت الخام).

المكالمة من Apple Watch (قيد الإعداد)

لا يستخدم تطبيق الساعة MTProto. فالساعة تسجّل الدخول وتستلم قائمة البوتات ونافذة التأكيد وتتصل عبر واجهة الساعة البرمجية (HTTPS JSON) في الخادم. ويُجري الخادم قرار القبول نفسه الذي يجريه لمكالمة التطبيق، ثم يعطي الساعة تذكرة لمرة واحدة (15 ثانية) لا تصلح إلا لتلك المكالمة، ويسلّم المكالمة إلى البوابة. وتتصل الساعة بتلك التذكرة بـنقطة دخول الصوت (WSS، pabal-voice-edge)، فتصل نقطة الدخول الصوت بعملية البوابة المسؤولة عن تلك المكالمة. لا يوجد تبادل مفاتيح (DH) ولا ترحيل مكالمات (coturn). ومع ذلك تفك البوابة تشفير الصوت وتسلّمه إلى النموذج كما في غيرها، فليست المكالمة مشفّرة من طرف إلى طرف، ويذهب الصوت إلى الجهة نفسها. ويستلم مالك البوت voice_call مطابقاً لمكالمة التطبيق (لا يختلف إلا توقيت started، انظر أدناه).

تفعيل المكالمات الصوتية

تفعيل المكالمات الصوتية يقوم به إنسان لدى @BotFather — لأن الموافقة على قواعد البوتات الصوتية تعهّد من مالك البوت وليست من الرمز المميّز. أما باقي الإعدادات فيمكن ضبطها من @BotFather أو عبر Bot API.

  1. اطلب من المشغّل السماح للبوت. على الخادم الذي لا يستقبل إلا البوتات المسموح لها (الافتراضي)، إلى أن يسمح المشغّل بالبوت بمعرّفه (الرقم الذي قبل : في الرمز المميّز) من تبويب الصوت في صفحة الإدارة، لا يظهر زر المكالمات الصوتية في @BotFather، وتغيير الإعدادات يعطي 403 VOICE_NOT_AVAILABLE. وإن لم يتم الإطلاق العام بعد فاطلب أيضاً إضافة حسابك الذي ستجرّب به الاتصال إلى المُختبِرين (معرّف المستخدم الخاص بك هو message.from.id في getUpdates بعد أن ترسل رسالة إلى البوت).
  2. حدّد التعليمات. وهي نص يصف ما يفعله البوت في المكالمة. اضبطها بـsetVoiceCallSettings أدناه، أو أرسلها عبر زر التعليمات (지시문) في شاشة المكالمات الصوتية لدى @BotFather. ولا يمكن التفعيل من دون تعليمات (باستثناء وضع الصوت الخام).
  3. في @BotFather: /mybots ← البوت ← المكالمات الصوتية (음성 통화) ← تشغيل (켜기). في المرة الأولى تظهر قواعد البوتات الصوتية، وعند الضغط على الموافقة على القواعد والتشغيل (규칙에 동의하고 켜기) تُفعَّل. ومن المرة التالية لا يسأل إلا «هل أشغّلها؟» (켤까요?).
  4. انتظر السماح مجدداً. على الخادم الذي لا يستقبل إلا البوتات المسموح لها، يعني السماح أنه سُمح بالتعليمات والتحية والأدوات كما كانت وقتها، فإذا كتبت التعليمات بعد السماح تصبح الحالة unavailable_reason: "awaiting_review". ولا يستقبل البوت المكالمات حتى يراجعها المشغّل ويسمح بها مجدداً. ويتكرر ذلك كلما غيّرت أحد هذه الثلاثة (أو عنوان الصوت الخام في وضع الصوت الخام). وإذا لم تكن لغة البوت الكورية أو الإنجليزية فلا يستقبل المكالمات إلا بعد أن يقرأ المشغّل التعليمات والتحية ويسمح له، حتى على خادم يستقبل كل البوتات (الأصوات واللغات).
  5. تحقّق من أن available في getVoiceCallSettings هو true، ثم جرّب الاتصال بزر الاتصال في الملف الشخصي للبوت من إصدار تطبيق يتضمن نافذة التأكيد.
켜기
BotFather음성 통화를 켜기 전에 음성 봇 규칙을 확인해 주세요. 1. 봇은 언제나 AI 라고 밝혀요. 사람인 척하게 할 수 없어요. 2. 실제 기관·회사·사람을 사칭할 수 없어요. 3. 카드 번호·주민등록번호·비밀번호·인증 번호를 묻게 할 수 없어요. 4. 통화 소리는 파발이 저장하지 않고, 대답을 만들기 위해 Google(Vertex AI)로 보내져요. 5. 봇과의 음성 통화는 만 19세 이상만 할 수 있어요. 6. 규칙을 어기면 운영자가 음성 통화나 봇을 멈출 수 있어요.
규칙에 동의하고 켜기
BotFather음성 통화를 켰어요. 음성 통화 · @order_helper_bot 상태: 켜짐 목소리: Kore (단단한) · 언어: 한국어 … 전화를 받으면 먼저 "AI 음성 봇과 연결되었습니다" 안내가 나가고, 그다음 봇이 인사해요.

يرد BotFather حالياً باللغة الكورية. في المحادثة أعلاه، بعد أن تضغط تشغيل يعرض BotFather قواعد البوتات الصوتية:

  1. يفصح البوت دائماً عن أنه ذكاء اصطناعي. ولا يمكن جعله يتظاهر بأنه إنسان.
  2. لا يجوز انتحال صفة جهات أو شركات أو أشخاص حقيقيين.
  3. لا يجوز جعله يطلب أرقام البطاقات أو أرقام الهوية الوطنية أو كلمات المرور أو رموز التحقق.
  4. لا يخزّن Pabal صوت المكالمة، ويُرسل الصوت إلى Google (Vertex AI) لتوليد الردود.
  5. لا يُسمح بالمكالمات الصوتية مع البوت إلا لمن هم في التاسعة عشرة أو أكثر.
  6. إذا خُرقت القواعد يمكن للمشغّل إيقاف المكالمات الصوتية أو البوت.

وبعد أن تضغط الموافقة على القواعد والتشغيل يعلن BotFather أن المكالمات الصوتية فُعّلت ويعرض شاشة الحالة (البوت @order_helper_bot، الحالة: مفعّل، الصوت: Kore (متين) · اللغة: الكورية …) مع تنبيه بأنه عند استقبال مكالمة يُبث أولاً الإعلان "AI 음성 봇과 연결되었습니다" (بالإنجليزية: «You're now talking to an AI voice bot.») ثم يحيّي البوت المتصل.

على الخادم الذي لا يستقبل إلا البوتات المسموح لها، قد تظهر الحالة هكذا: "켜짐 — 바뀐 지시문·인사말을 운영자가 확인할 때까지 전화를 받지 않아요" (مفعّل — لا يستقبل المكالمات حتى يراجع المشغّل التعليمات والتحية المعدّلة) (الخطوة 4 أعلاه). وإذا لم تكن لغة البوت الكورية أو الإنجليزية فستظهر الحالة على أي خادم، إلى أن يسمح المشغّل للبوت، هكذا: "켜짐 — 한국어·영어가 아닌 봇은 운영자가 지시문·인사말을 읽고 허용할 때까지 전화를 받지 않아요 (자동 규칙 검사가 한국어와 영어만 읽어요)" (مفعّل — البوت الذي ليست لغته الكورية أو الإنجليزية لا يستقبل المكالمات حتى يقرأ المشغّل التعليمات والتحية ويسمح له؛ فالفحص الآلي للقواعد لا يقرأ إلا الكورية والإنجليزية).

في شاشة المكالمات الصوتية أزرار للتشغيل والإيقاف، والتعليمات، والتحية، والصوت، واللغة، والحدود، والاستخدام، ونص المحادثة، ومفتاح المالك، ووضع الصوت الخام، و(على خادم فيه MCP مفعّل) خوادم MCP. وحتى بعد تعطيل الصوت أو إلغاء السماح، التعطيل والحذف ممكنان دائماً — تعطيل الصوت، وحذف مفتاح المالك، وتعطيل وضع الصوت الخام، وحذف خادم MCP.

عند التفعيل وفي كل مكالمة يخضع اسم البوت أيضاً لفحص انتحال الشخصية. والبوت الذي يحمل اسم جهة أو شركة حقيقية لا يُفعَّل، ويُرفض بـVOICE_CONTENT_REFUSED: name: impersonation. وإن كان البوت فعلاً تابعاً لتلك الجهة فأبلغ دعم الخادم.

الإعدادات — setVoiceCallSettings

تضبط إعدادات الصوت للبوت. وهي تعديل جزئي لا يتغيّر فيه إلا ما أرسلته، ويُفحص الكل ثم يُكتب مرة واحدة أو يُرفض كله. وإذا لم يتغيّر شيء فلا يُكتب شيء.

curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallSettings" -H 'Content-Type: application/json' -d '{
  "instructions": "أنت موظف الرد على الهاتف في متجر Order Helper. أجب باختصار وبأدب. يفتح المتجر من العاشرة صباحاً حتى الثامنة مساءً.",
  "greeting": "معك Order Helper. كيف يمكنني مساعدتك؟",
  "voice": "kore",
  "language": "ko-KR",
  "max_call_duration": 180
}'
# {"ok":true,"result":true}
المعاملالنوعالوصف
enabledBooleanالتشغيل · الإيقاف. للتشغيل يجب أن تكون قد وافقت على القواعد لدى @BotFather (وإلا 403 VOICE_RULES_NOT_ACCEPTED)، وفي الوضع المُدار يجب أن توجد تعليمات. الإيقاف ممكن دائماً — تستمر المكالمات الجارية حتى نهايتها ولا تُقبل مكالمات جديدة فقط
instructionsStringالتعليمات التي يتبعها النموذج. حتى 8,192 بايت UTF-8 (نحو 4,000 حرف عربي، أو 2,700 حرف كوري). و"" تعني الحذف — ولا يمكن إفراغها لبوت مفعّل في الوضع المُدار. كلما طالت زادت تكلفة المكالمة، ويمكن للمتصل أن يستخرجها، فلا تضع فيها كلمات مرور أو مفاتيح
greetingStringأول ما يقوله البوت بعد إعلان الذكاء الاصطناعي. حتى 512 بايت (نحو 250 حرفاً عربياً، أو 170 حرفاً كورياً). وإذا كانت "" يحيّي النموذج باختصار من تلقاء نفسه
voiceStringمعرّف الصوت (القائمة)، الافتراضي kore
languageStringاللغة التي يتكلم بها البوت ويفهمها، وهي واحد من 9 معرّفات (الافتراضي ko-KR، القائمة). ويُحفظ zh-CN على أنه cmn-CN، والمعرّف الذي لا يختلف إلا في الأحرف الكبيرة والصغيرة (ja-jp) يُحفظ بالشكل الوارد في القائمة. وإذا لم تكن الكورية أو الإنجليزية فلا يستقبل البوت المكالمات إلا بعد أن يقرأ المشغّل التعليمات والتحية ويسمح له. أما إعلان الذكاء الاصطناعي في بداية المكالمة فلا يتبعها، بل يأتي بلغة نافذة التأكيد التي رآها المتصل
max_call_durationIntegerأقصى مدة للمكالمة الواحدة (بالثواني). من 60 فأكثر، ولا تتجاوز limits.max_call_duration (الأصغر بين سقف المشغّل وسقف الخادم، الافتراضي 600). والقيمة الافتراضية للبوت 300
max_concurrent_callsIntegerعدد المكالمات التي تُستقبل في الوقت نفسه. من 1 فأكثر، ولا يتجاوز limits.max_concurrent_calls (الافتراضي 2). والقيمة الافتراضية للبوت 2
toolsArrayأدوات البوت التي يمكن للنموذج استدعاؤها أثناء المكالمة (الأدوات). و[] تحذفها كلها
transcriptBooleanمفتاح تشغيل نص المحادثة (نص المحادثة)
mode، stream_url، stream_secretStringوضع الصوت الخام (إعداد وضع الصوت الخام)
model، api_keyغير مقبولة — VOICE_FIELD_NOT_AVAILABLE. فالنموذج يحدده الخادم، والمفتاح لا يُضبط إلا عبر setVoiceCallApiKey
  • قواعد المحتوى: تُفحص التعليمات والتحية ووصف الأدوات عند الحفظ وعند المكالمة. ويُرفض بـVOICE_CONTENT_REFUSED: <field>: <code> كلٌّ من: جعله يقول إنه إنسان (claims_human)، وجعله يخفي أنه ذكاء اصطناعي (hides_ai)، والأمر بتجاهل قواعد Pabal (overrides_rules)، وانتحال صفة جهة أو شركة حقيقية (impersonation)، والتقديم على أنه جهة رسمية (official_claim)، وطلب أرقام البطاقات · الهوية الوطنية · كلمات المرور · رموز التحقق (asks_secrets)، والنص الذي يشبه مفتاحاً سرياً (looks_like_secret)، والأحرف غير المرئية (hidden_text). أما الجمل النافية مثل «لا تطلب كلمة المرور أبداً» فتمر.
  • قواعد Pabal الأساسية تأتي أولاً دائماً. في كل مكالمة يقرأ النموذج بالترتيب: قواعد Pabal ← تعليمات مالك البوت ← خاتمة قواعد Pabal، وتوضع التعليمات داخل خانة مُعلَّمة بأنها «نص مالك البوت». ولا يمكن للتعليمات أن تحل محل قواعد Pabal أو تتقدم عليها.
  • يمكن التغيير 30 مرة في الساعة (محسوبة مع @BotFather، ولا يُحتسب إلا ما تغيّر فعلاً). وإذا تجاوزتها تصلك 429 Too Many Requests: retry after N وparameters.retry_after.
  • عند الإرسال كنموذج (-d name=value) تُمرَّر tools سلسلةَ JSON.
  • البوت الذي يسجّل الدخول عبر MTProto فقط يرسل JSON نفسه عبر bots.sendCustomRequest (بوتات MTProto).

عرض الإعدادات الحالية — getVoiceCallSettings

curl -s "https://pabal.me/bot$BOT_TOKEN/getVoiceCallSettings"
{
  "ok": true,
  "result": {
    "enabled": true,
    "available": true,
    "suspended": false,
    "rules_accepted": true,
    "instructions": "أنت موظف الرد على الهاتف في متجر Order Helper. …",
    "greeting": "معك Order Helper. كيف يمكنني مساعدتك؟",
    "voice": "kore",
    "language": "ko-KR",
    "max_call_duration": 180,
    "max_concurrent_calls": 2,
    "limits": {"max_call_duration": 600, "max_concurrent_calls": 2, "bot_minutes_per_day": 60, "owner_minutes_per_day": 120},
    "voices": [{"id": "zephyr", "name": "Zephyr", "description": "Bright"}, …],
    "languages": ["ko-KR", "en-US", "ja-JP", "cmn-CN", "fr-FR", "es-US", "id-ID", "vi-VN", "ar-EG"],
    "testers_only": true,
    "mode": "managed",
    "has_stream_secret": false,
    "owner_terms_accepted": false,
    "stream_available": false,
    "updated_date": 1790380712,
    "tools": [],
    "tool_limits": {"max_tools": 16, "max_definition_bytes": 8192, "definition_bytes": 0, "mcp_tools": 0},
    "tool_delivery": "none",
    "transcript": false,
    "transcript_available": false,
    "transcript_delivery": "get_updates",
    "has_api_key": false,
    "own_keys_available": false,
    "mcp_available": false
  }
}
الحقلالمعنى
enabledهل فعّله مالك البوت
available، unavailable_reasonهل يستطيع البوت الآن استقبال مكالمة من أي أحد، وإن لم يستطع فالسبب (الجدول أدناه)
rules_accepted، owner_terms_acceptedهل وُوفق على قواعد البوتات الصوتية، وعلى شروط مالكي البوتات الحالية (النسخة 4)
limitsالقيم القصوى التي يمكن ضبطها على هذا الخادم والحدود اليومية (بالدقائق)
voices، languagesالأصوات واللغات التي يمكن اختيارها
testers_onlytrue إذا لم يتم الإطلاق العام بعد فلا يتصل إلا المُختبِرون الذين حدّدهم المشغّل
tools، tool_limits، tool_delivery، tool_delivery_problemالأدوات وحدودها، والوجهة التي تذهب إليها استعلامات الأدوات الآن (webhook·get_updates·mtproto·none) وسبب تعذّر وصولها (استلام استعلامات الأدوات)
mode، stream_url، stream_secret_fingerprint، has_stream_secret، stream_available، stream_ignoresوضع الصوت الخام — لا يظهر السرّ، بل بصمة من 8 خانات فقط
transcript، transcript_available، transcript_unavailable_reason، transcript_delivery، transcript_delivery_problemمفتاح تشغيل نص المحادثة، وهل سيُسلَّم فعلاً الآن
has_api_key، api_key، own_keys_availableمفتاح النموذج الخاص بالمالك — لا يظهر المفتاح، بل المزوّد · البصمة · تاريخ الضبط · وهل يمكن استخدامه فقط
mcp_available، mcp_servers، mcp_unavailable_reasonخوادم MCP
outboundحالة المكالمات من البوت (المكالمات من البوتات — الإعدادات والاستخدام)
updated_dateوقت آخر تغيير (بتوقيت يونكس بالثواني)
unavailable_reasonالمعنى
voice_offالصوت معطّل على هذا الخادم، أو لا يوجد بعد قسم مكالمات البوتات في سياسة الخصوصية
kill_switchأوقف المشغّل كل المكالمات الصوتية
bot_voice_offلم يفعّله مالك البوت
not_allowedلم يسمح المشغّل لهذا البوت
awaiting_reviewلم يراجع المشغّل بعد التعليمات · التحية · الأدوات المعدّلة (أو عنوان الصوت الخام في وضع الصوت الخام). وإذا لم تكن لغة البوت الكورية أو الإنجليزية فيجب أن يقرأ المشغّل التعليمات والتحية ويسمح للبوت، حتى على خادم يستقبل كل البوتات
suspendedأوقف المشغّل صوت هذا البوت (أو حساب مالكه)
name_refused، text_refusedاسم البوت، أو التعليمات · التحية · وصف الأدوات المحفوظة، لا يتوافق مع قواعد المحتوى الحالية. ويظهر text_refused أيضاً إذا حُذفت اللغة التي اختارها البوت من القائمة فلم يعد استخدامها ممكناً — فاختر لغة من جديد
no_relay، no_gatewayلا يوجد على الخادم ترحيل مكالمات أو بوابة صوت
no_gateway_with_tools، no_own_key_gatewayلا توجد بوابة تستطيع استخدام أدوات هذا البوت أو مفتاح مالكه
no_gateway_with_languageلا توجد بوابة متصلة تُجري المكالمات بلغة هذا البوت (الأصوات واللغات)
own_key_unavailable، stream_unavailableمفتاح المالك أو وضع الصوت الخام المضبوط لا يمكن استخدامه الآن، فلا تُستقبل المكالمات (ولا تُستقبل بمفتاح المنصة بدلاً منه)

الأصوات واللغات

تختار من بين 30 صوتاً جاهزاً في Gemini Live (الافتراضي kore). ولا توجد ميزة لرفع الأصوات أو استنساخها. وفي زر الصوت (목소리) لدى @BotFather تختار بالاسم والطابع.

idالطابعidالطابعidالطابع
zephyrمشرقpuckمرحcharonواضح
koreمتينfenrirمتحمّسledaفتيّ
orusمتينaoedeمنعشcallirrhoeهادئ الإيقاع
autonoeمشرقenceladusتتخلله أنفاسiapetusصافٍ
umbrielهادئ الإيقاعalgiebaناعمdespinaناعم
erinomeصافٍalgenibأجشّrasalgethiواضح
laomedeiaمرحachernarرقيقalnilamمتين
schedarرزينgacruxناضجpulcherrimaمبادر
achirdودودzubenelgenubiمريحvindemiatrixحنون
sadachbiaحيويsadaltagerواسع المعرفةsulafatدافئ

اللغة (language) واحدة من التسع أدناه. وهي اللغة التي يتكلم بها البوت ويفهمها، وبها يجيب. وفي @BotFather تختارها بزر اللغة (언어) في شاشة المكالمات الصوتية.

idاللغةما ينبغي معرفته
ko-KRالكوريةالافتراضي
en-USالإنجليزية—
ja-JPاليابانية—
cmn-CNالصينية (الماندرين)ليست في قائمة Google الرسمية للغات المدعومة، فلا تضمن Google جودتها (وفي الاختبارات كان الفهم والإجابة كلاهما دقيقين). وتُقبل أيضاً إذا أُرسلت بالمعرّف zh-CN، وتُحفظ على أنها cmn-CN
fr-FRالفرنسية—
es-USالإسبانية—
id-IDالإندونيسية—
vi-VNالفيتنامية—
ar-EGالعربية (الفصحى)يتكلم البوت العربية الفصحى المعاصرة. والرمز هو ar-EG، وهو رمز العربية الوحيد في نموذج Google الذي يعمل في الوقت الحقيقي، لكنه لا يحدد لهجة — فلا يتكلم البوت بلهجة ما إلا إذا طلبتها التعليمات
  • البوت الذي ليست لغته الكورية أو الإنجليزية لا يستقبل المكالمات إلا بعد أن يقرأ المشغّل تعليماته وتحيته ويسمح له — حتى على خادم يستقبل كل البوتات. والسبب أن الفحص الآلي للقواعد عند الحفظ (ادّعاء أنه إنسان، وانتحال الصفة، وغير ذلك) لا يقرأ إلا الكورية والإنجليزية. وقبل السماح تكون الحالة unavailable_reason: "awaiting_review"، وإذا غيّرت التعليمات أو التحية أو الأدوات بعد السماح عاد البوت ينتظر السماح مجدداً. وينبّه @BotFather إلى ذلك في شاشة اختيار اللغة وبعد الاختيار: "한국어·영어가 아닌 언어는 자동 규칙 검사가 읽지 못해서, 운영자가 지시문·인사말을 읽고 허용한 뒤에 전화를 받아요." (الفحص الآلي للقواعد لا يقرأ اللغات غير الكورية والإنجليزية، لذا لا يستقبل البوت المكالمات إلا بعد أن يقرأ المشغّل التعليمات والتحية ويسمح له). وفي تبويب الصوت في صفحة الإدارة لدى المشغّل يظهر ذلك البوت بعبارة "한국어·영어가 아닌 봇 (자동 규칙 검사가 읽지 못함): 지시문·인사말을 읽어 주세요" (بوت ليست لغته الكورية أو الإنجليزية، لا يقرؤه الفحص الآلي للقواعد: اقرأ التعليمات والتحية من فضلك).
  • لا يستقبل البوت المكالمات إلا إذا وُجدت بوابة تُجري المكالمات بلغة البوت. فالبوابة تُعلن للخادم عند اتصالها به لغات البوت التي تستطيع إجراء المكالمات بها (منذ الإصدار 0.7.0 يُعلن التسع كلها)، والبوابة التي لا تُعلن أي لغة تُعدّ مقتصرة على الكورية والإنجليزية. وإذا لم توجد بوابة تُجري المكالمات بلغة البوت تكون الحالة no_gateway_with_language ويختفي زر الاتصال. ولا تُجرى المكالمة بلغة أخرى بدلاً منها.
  • تُقبل المعرّفات دون تمييز بين الأحرف الكبيرة والصغيرة (ja-jp) وتُحفظ بالشكل الوارد في القائمة. والقيمة التي ليست في القائمة تُرفض بـVOICE_LANGUAGE_INVALID: language is one of ko-KR, en-US, ja-JP, cmn-CN, fr-FR, es-US, id-ID, vi-VN, ar-EG التي تذكر المعرّفات المقبولة.
  • إذا حُذفت اللغة التي اخترتها من القائمة لاحقاً يحتفظ البوت بإعداداته لكنه لا يستقبل المكالمات (text_refused). ولا يستقبلها بلغة أخرى بدلاً منها، فاختر لغة من جديد من زر اللغة (언어) في @BotFather.
  • لا يمكن بعدُ اختيار الخميرية لغةً للبوت. فالنموذج يفهمها، لكن إجاباته الحرة لم يتحقق منها بعدُ متحدث أصلي بالخميرية.

إذا ظل المتصل يتكلم بلغة أخرى فإن النموذج يرد بتلك اللغة. أما إعلان الذكاء الاصطناعي فلا يتبع language الخاصة بالبوت، بل يأتي بلغة نافذة التأكيد التي رآها المتصل (ما يراه المتصل).

استلام إشعارات المكالمات — voice_call

عندما يتصل أحد بالبوت يصل التحديث voice_call. وأنواع تحديثات الصوت الثلاثة (voice_call، voice_call_tool_query، voice_call_transcript) لا تصل إلا إذا طلبتها بكتابة أسمائها: ضعها في allowed_updates الخاصة بـ getUpdates أو setWebhook. ولا تصل مع القائمة الفارغة (أما الأنواع الأخرى فتصل كلها مع القائمة الفارغة).

curl -s "https://pabal.me/bot$BOT_TOKEN/getUpdates" -H 'Content-Type: application/json' \
  -d '{"allowed_updates": ["message", "voice_call", "voice_call_tool_query"], "timeout": 30}'
{
  "update_id": 7,
  "voice_call": {
    "id": "4611686018427387905",
    "direction": "incoming",
    "event": "started",
    "from": {"id": 100002, "is_bot": false, "first_name": "هناء", "language_code": "ko"},
    "date": 1790380803,
    "max_duration": 180
  }
}
{
  "update_id": 9,
  "voice_call": {
    "id": "4611686018427387905",
    "direction": "incoming",
    "event": "ended",
    "from": {"id": 100002, "is_bot": false, "first_name": "هناء", "language_code": "ko"},
    "date": 1790380952,
    "start_date": 1790380803,
    "duration": 149,
    "max_duration": 180,
    "end_reason": "caller"
  }
}
  • direction: "incoming" لمكالمة إلى البوت، و"outgoing" لمكالمة أجراها البوت (تحديثات المكالمات من البوتات). وقراءة غياب الحقل على أنه مكالمة إلى البوت، كما في السابق، تبقى صحيحة.
  • allowed_updates إعداد واحد لكل بوت، ويُحفظ آخر ما مرّره getUpdates أو setWebhook (وإذا لم تمرّره تبقى القيمة السابقة). لذلك إذا مرّرت قائمة كما في المثال أعلاه، فإن الأنواع الافتراضية غير المكتوبة في القائمة (edited_message، callback_query) لن تصل بعد ذلك — اكتب كل الأنواع التي يستخدمها البوت. وإشعارات المكالمات التي وقعت قبل الطلب لا تُحفظ، فلا تصل حتى لو طلبتها لاحقاً.
  • المتصل (from) لا يتضمن إلا المعرّف والاسم ولغة التطبيق. ولا يصل اسم المستخدم ولا رقم الهاتف (فهذا ما تعد به نافذة التأكيد). والحساب المحذوف يأتي بـ first_name: "Deleted Account".
  • مع webhook تصل بطلبات POST بالترتيب مثل التحديثات الأخرى. وقائمة انتظار getUpdates تُحفظ في الذاكرة حتى آخر 1,000 تحديث لكل بوت، وتضيع عند إعادة تشغيل الخادم.
  • البوت الموقوف أو المحذوف لا يصله شيء.
eventمتىحقول إضافية
startedعندما يؤكد تطبيق المتصل المكالمة (انتهى تبادل المفاتيح) ويُبلغ الخادم البوابة بأن المكالمة اتصلت. والخادم لا يرى الصوت، فهذا «اتصال» بحسب علم الخادم. أما مكالمة Apple Watch فلا تبادل مفاتيح فيها، فهو الوقت الذي تُبلغ فيه البوابة بأن صوت الساعة قد اتصلmax_duration
endedعند انتهاء مكالمة كانت متصلة. وإذا كان ينتظر نص المحادثة فقد يتأخر حتى 10 ثوانٍ. وعلى خادم فُعّل فيه الاحتفاظ بالمحادثة يتأخر حتى 10 ثوانٍ لكل المكالمات غير الصوت الخام مهما كان اختيار المتصل — حتى لا يعرف مالك البوت اختيار المتصلstart_date، duration (ثواني الاتصال بساعة الخادم)، max_duration، end_reason، و(إذا كان نص المحادثة مفعّلاً) transcript
missedمكالمة انتهت قبل أن تتصل (أُغلقت أثناء الرنين ونحو ذلك)end_reason
refusedفقط عندما يجتاز المتصل كل الفحوص لكن المكالمة لا تُستقبل لسبب من جهة البوت (المكالمات المتزامنة، أو الوقت اليومي، أو البوابة) (ولا يُبلَّغ مالك البوت بأنواع الرفض الأخرى)refused_reason: busy (امتلأت المكالمات المتزامنة) · budget (نفد الوقت اليومي للبوت · المالك · الخادم) · unavailable (امتلأت البوابات القادرة على الاستقبال)

end_reason: caller (أنهاها المتصل) · bot (أنهاها البوت، endVoiceCall) · max_length (بلغت الحد الأقصى) · failed (فشل الاتصال) · operator (المشغّل · مفتاح الإيقاف الطارئ) · suspended (إيقاف البوت · حذفه).

لكل مكالمة سُلّمت إلى البوابة يبقى سجل مكالمة (مدة المكالمة أو «مكالمة ملغاة») في المحادثة بين المتصل والبوت (ولا يبقى للمكالمات المرفوضة قبل الاستقبال). ولا تصل نسخة البوت منه في صورة تحديث message — فسجل البوت هو voice_call. وإذا اختار المتصل الاحتفاظ بالمحادثة يبقى نص المحادثة في محادثة المتصل وحدها مباشرةً تحت سجل المكالمة، ولا يبقى شيء في جهة البوت. وعلى الخادم الذي فُعّل فيه الاحتفاظ بالمحادثة تُحجز مسبقاً 30 رقماً من أرقام الرسائل بعد سجل المكالمة، فيكون رقم الرسالة التالية للسجل أكبر من رقم السجل بـ 31 على الأقل (وكذلك في المكالمات التي لم يُحتفظ بها).

أثناء المكالمة

إنهاء المكالمة — endVoiceCall

curl -s "https://pabal.me/bot$BOT_TOKEN/endVoiceCall" -d voice_call_id=4611686018427387905
  • farewell (Boolean، الافتراضي true): إذا كانت المكالمة متصلة يودّع النموذج بإيجاز ثم ينهيها. وإذا لم تنتهِ خلال 10 ثوانٍ ينهيها الخادم. وfalse تعني الإنهاء فوراً. أما مكالمات وضع الصوت الخام فتنتهي فوراً دائماً.
  • voice_call.end_reason للمكالمة المنتهية هو bot. وإذا لم تكن مكالمة جارية لهذا البوت فالنتيجة 400 VOICE_CALL_NOT_FOUND.

إعطاء النموذج ملاحظة — sendVoiceCallContext

تخبر النموذج بما يحتاج إلى معرفته أثناء المكالمة. ويقرأ النموذج هذا على أنه نص مالك البوت لا كلام المتصل، وتبقى قواعد Pabal أولاً.

curl -s "https://pabal.me/bot$BOT_TOKEN/sendVoiceCallContext" -d voice_call_id=4611686018427387905 \
  --data-urlencode "text=ألغى هذا المتصل الطلب A-1042 للتو. إذا سأل فأخبره بذلك." -d respond=false
  • text: حتى 2,000 حرف (6,144 بايت)، وتُفحص وفق قواعد المحتوى. وإذا كانت respond=true يتفاعل النموذج فوراً، وإذا كانت false (الافتراضي) يستخدمها في دوره التالي.
  • تذهب الملاحظة أيضاً إلى النموذج (وفي المكالمات المُدارة إلى Google)، وقد يقولها النموذج للمتصل — فلا تضع فيها إلا ما يجوز أن يعرفه المتصل.
  • قبل الاتصال 400 VOICE_CALL_NOT_ACTIVE، ولمكالمات الصوت الخام أو البوابات التي لا تقبل الملاحظات 400 VOICE_CONTEXT_NOT_SUPPORTED، وإذا تجاوزت 10 ملاحظات في الدقيقة للمكالمة الواحدة 429 (retry_after ‏60).

الاستخدام — getVoiceCallUsage

curl -s "https://pabal.me/bot$BOT_TOKEN/getVoiceCallUsage?period=today"
{"ok": true, "result": {"period": "today", "from_date": 1790348400, "to_date": 1790434799,
 "calls": 3, "seconds": 412, "missed": 1,
 "refused": {"busy": 0, "budget": 0, "unavailable": 0},
 "platform_seconds": 412, "own_key_seconds": 0,
 "remaining_seconds_today": {"bot": 3188, "owner": 6788}}}
  • period: today (الافتراضي) أو month. ومع month يمكنك اختيار الشهر مثل month=2026-09 (والشهر المستقبلي يعطي VOICE_USAGE_PERIOD_INVALID).
  • حدود اليوم والشهر وفق المنطقة الزمنية للخادم (الافتراضي Asia/Seoul). وremaining_seconds_today هو الحد اليومي مطروحاً منه المكالمات المنتهية والوقت الذي تحجزه المكالمات الجارية الآن.
  • يُحتفظ بسجل استخدام كل مكالمة (دون تسجيل المتصل) مدة 180 يوماً بعد انتهاء ذلك الشهر، وتبقى المجاميع الشهرية ما دام البوت موجوداً.

الأدوات — استدعاء شيفرة البوت أثناء المكالمة

عندما يسأل المتصل «أين وصل الطلب A-1042؟» يستدعي النموذج أداة البوت check_order، ويسلّم الخادم ذلك الاستعلام إلى البوت، ثم ينقل النموذج رد البوت كلاماً. يعرّف مالك البوت الأدوات، وتجيب عنها شيفرة البوت.

النموذج · بوابة الصوت ⁧خادم Pabal⁩ شيفرة البوت ① check_order {"order_id": "A-1042"} فحص الوسائط · حذف غير المعلن رقم بطاقة أو هوية: يتوقف هنا ② voice_call_tool_query · budget_ms ⁧صمت: «⁦One moment, let me check.⁩»⁩ ⁧5ث: «⁦Just a moment longer, please.⁩»⁩ ③ answerVoiceCallToolQuery(result) فحص الرد · رفض الأسرار والبطاقات ⁧حتى 16,384 بايت⁩ ⁧④ tool_result (بيانات فقط)⁩ ينقل النموذج النتيجة كلاماً ⁧إذا تأخر: «⁦The tool did not answer in time.⁩»⁩
استعلام أداة واحد: النموذج ← الخادم ← البوت ← الخادم ← النموذج

شرح المخطط

  • ثلاثة أعمدة: على اليسار النموذج وبوابة الصوت، وفي الوسط خادم Pabal، وعلى اليمين شيفرة مالك البوت. والخطوط المنقطة العمودية هي الزمن. لا يتحدث النموذج مع البوت مباشرة، بل تمر كل الاستعلامات والردود عبر الخادم.
  • الخانة الرمادية في ① — يفحص الخادم الوسائط أولاً: هل تطابق parameters الخاصة بالأداة، والحجم والعمق، والمفاتيح غير المعلنة (تُحذف ثم يُسلَّم الباقي)، والقيم التي تشبه أرقام البطاقات أو الهوية الوطنية (لا تصل إلى البوت، ويسمع النموذج رفضاً).
  • ما يستلمه البوت في ② هو معرّف الاستعلام، ومعرّف المكالمة، والمتصل، واسم الأداة، والوسائط (سلسلة JSON)، وbudget_ms — عدد المللي ثانية المتبقية حتى الموعد الذي يجب أن يصل فيه الرد إلى الخادم. وأثناء الانتظار تملأ البوابة الصمت (الخانة اليسرى).
  • رد ③ يُفحص أيضاً: ما يشبه الأسرار، والكلام الذي يدفع إلى خرق القواعد، وأرقام البطاقات أو الهوية الوطنية تُرفض ويسمع النموذج خطأً. الرد الأول هو الأخير، فحتى الرد المرفوض يُغلق الاستعلام.
  • في ④ الخط الأزرق المتصل هو الوضع الطبيعي، والخط الأحمر المنقط هو حالة تجاوز الموعد. ويستلم النموذج نتيجة الأداة بوصفها بيانات فقط لا تعليمات، وإذا فشلت يسمع عبارة قصيرة من Pabal ثم يشرح الوضع للمتصل.

تعريف الأدوات

curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallSettings" -H 'Content-Type: application/json' -d '{
  "instructions": "أنت موظف الرد على الهاتف في متجر Order Helper. أجب باختصار وبأدب. إذا ذكر المتصل رقم طلب فاستخدم الأداة check_order لتعرف حالة الشحن وأخبره بها.",
  "tools": [{
    "name": "check_order",
    "description": "تبحث عن حالة الشحن برقم الطلب. استدعِها عندما يذكر المتصل رقم طلب.",
    "parameters": {"type": "object",
                   "properties": {"order_id": {"type": "string", "description": "رقم الطلب، مثال: A-1042"}},
                   "required": ["order_id"]},
    "timeout_ms": 5000
  }]
}'
البندالقاعدة
المفاتيحأربعة فقط: name، description، parameters (اختياري)، timeout_ms (اختياري)
العدد · الحجمأدوات البوت وأدوات MCP المثبّتة معاً 16، ومجموع UTF-8 للتعريفات (name·description·parameters) 8,192 بايت. والأمر نفسه لبوتات مفتاح المالك
name[A-Za-z_][A-Za-z0-9_-]{0,63}. لا يجوز أن يحتوي على __ (يُستخدم في أسماء أدوات MCP)، ولا أن يبدأ بـ pabal، ولا أن يتكرر مع تجاهل حالة الأحرف
descriptionإلزامي، حتى 1,024 بايت، ويُفحص وفق قواعد المحتوى. وبه يقرر النموذج متى يستدعي الأداة، فاكتبه بدقة
parametersالمستوى الأعلى {"type": "object"}. وإذا حذفته فالمعنى «بلا وسائط»
timeout_msمن 1,000 إلى 15,000، الافتراضي 5,000. أقصى وقت ينتظر فيه النموذج الرد

لا تستخدم parameters إلا جزءاً من JSON Schema يقبله المحرّكان كلاهما.

  • الكلمات المفتاحية عشر فقط: type، description، enum، properties، required، items، minimum، maximum، minItems، maxItems. وtype قيمة واحدة (string·number·integer·boolean·array·object).
  • properties·required للـ object، وitems·minItems·maxItems للـ array (وitems إلزامي)، وminimum·maximum للأعداد، وenum للـ string فقط (من 1 إلى 32 قيمة، كل منها 64 بايت).
  • أسماء الخصائص [A-Za-z_][A-Za-z0-9_]{0,63}، و24 خاصية للأداة الواحدة، و3 مستويات للـ object · و5 مستويات للـ object والـ array معاً. ووصف المعامل 512 بايت.
  • تُرفض المعاملات التي تطلب أسراراً: إذا احتوى الاسم على كلمات مثل pin·cvv·password·otp·ssn·rrn·cardnumber، أو تحدث الوصف عن رقم بطاقة · كلمة مرور · رمز تحقق · رقم هوية وطنية، فالنتيجة asks_secrets.
  • يُبلغ الخطأ عن أول مشكلة مع مسارها: VOICE_TOOL_INVALID: tools[0].parameters.properties.tags: items_missing، VOICE_TOOL_INVALID: tools[0].name: reserved، VOICE_CONTENT_REFUSED: tools[0].description: impersonation.
  • لا تُعرَّف الأدوات إلا عبر Bot API. وشاشة المكالمات الصوتية في @BotFather تعرض أسماء الأدوات فقط.
  • على الخادم الذي لا يستقبل إلا البوتات المسموح لها، إذا أضفت أدوات أو غيّرتها لا يستقبل البوت المكالمات حتى يسمح المشغّل مجدداً (awaiting_review، الخطوة 4 في التفعيل).

استلام استعلامات الأدوات

{
  "update_id": 813,
  "voice_call_tool_query": {
    "id": "8190448532776044545",
    "voice_call_id": "4611686018427387905",
    "from": {"id": 100002, "is_bot": false, "first_name": "هناء", "language_code": "ko"},
    "name": "check_order",
    "arguments": "{\"order_id\":\"A-1042\"}",
    "budget_ms": 4700
  }
}
  • arguments هي نصّ كائن JSON (حتى 4,096 بايت · وعمق 8، وقد فحصها الخادم). ومع ذلك فهي قيمة صنعها النموذج من كلام المتصل، فليتحقق منها البوت من جديد. ولا يجوز أن يستطيع أحد سماع طلبات غيره لمجرد معرفة رقم الطلب — لا تُخبر إلا بما يخص from.id (المتصل الذي تحقق منه Pabal).
  • budget_ms هو عدد المللي ثانية من لحظة استلام البوت حتى الموعد الذي يجب أن يصل فيه الرد إلى الخادم. وهو timeout_ms الخاص بالأداة مطروحاً منه طريق العودة إلى النموذج (200 ms) وطريق الوصول إلى البوت (100 ms)، فيكون عادةً أقل من timeout_ms بنحو 300 ms. ولا تُستخدم ساعة أي من الطرفين.

الطريق الذي يصل به الاستعلام إلى البوت يُحدَّد بهذا الترتيب (getVoiceCallSettings.tool_delivery).

  1. إذا وُجد webhook فعبره. ويجب أن تتضمن allowed_updates الخاصة بـ webhook القيمة voice_call_tool_query، وإلا فلا يذهب الاستعلام عبر طريق آخر ولا يصل (tool_delivery_problem: "not_in_allowed_updates"). والاستعلام طلب POST يُرسل منفصلاً، فلا ينحبس في قائمة الترتيب، ولا يُعاد إرساله.
  2. إذا لم يوجد webhook وكنت قد استدعيت getUpdates خلال آخر 15 ثانية، فيأتي في مقدمة getUpdates التالي. ويجب أيضاً أن يكون في allowed_updates. وحد الاستعلامات المنتظرة 8 للبوت الواحد.
  3. وإلا فإن كان البوت متصلاً عبر MTProto فعبر updateBotWebhookJSONQuery (بغض النظر عن allowed_updates).
  4. وإن لم يتحقق شيء من ذلك فلا يصل (tool_delivery_problem: "not_listening")، ويسمع النموذج «The tool could not be reached.» (تعذّر الوصول إلى الأداة).

الرد — answerVoiceCallToolQuery

المعاملالوصف
tool_query_idvoice_call_tool_query.id الوارد في التحديث
resultالنتيجة. نص، أو كائن · مصفوفة إن كان الجسم JSON (وحتى لو أرسلته نصاً فإنه يُعامل كـ JSON إذا أمكن قراءته كائناً · مصفوفة JSON). حتى 16,384 بايت UTF-8 (ويمكن للمشغّل خفضه)
errorعبارة الفشل الخاصة بالبوت، حتى 512 بايت. وينقل النموذج هذه العبارة إلى المتصل
  • واحد بالضبط من result وerror (إن غابا معاً TOOL_RESULT_EMPTY، وإن وُجدا معاً TOOL_RESULT_INVALID). والقيمة المعادة True.
  • يذهب رد الأداة إلى النموذج (وفي المكالمات المُدارة إلى Google)، وينقل النموذج محتواه إلى المتصل — فلا تضع فيه إلا ما يجوز أن يعرفه المتصل.
  • الرد الأول هو الأخير. والاستعلام المتأخر أو المنتهي يعطي 400 TOOL_QUERY_EXPIRED: <reason> — answered (أُجيب عنه)، timeout، cancelled (قاطع المتصل فسحبه النموذج)، call_ended، unreachable. والمعرّف المجهول أو الذي مضت عليه 10 دقائق يعطي TOOL_QUERY_ID_INVALID.
  • يُفحص الرد أيضاً: تُحذف الأحرف غير المرئية، ويُرفض بـTOOL_RESULT_REFUSED: <code> ما يشبه الأسرار (looks_like_secret)، والكلام الذي يدفع إلى خرق القواعد (overrides_rules·hides_ai·claims_human·asks_secrets)، وأرقام البطاقات · الهوية الوطنية (sensitive_data). أما بيانات مالك البوت مثل عنوان المتجر · هاتفه · روابطه فتمر. وإذا تجاوز الحجم TOOL_RESULT_TOO_LONG·TOOL_ERROR_TOO_LONG.
  • مع webhook يمكنك الرد في جسم الاستجابة — يكفي أن تضع هذا في استجابة 200 لطلب POST الخاص باستعلام الأداة (ولا تُنفَّذ الطرق الأخرى). وقبل الرد تحقق حتماً من الترويسة X-Telegram-Bot-Api-Secret-Token. فإذا وضعت بيانات العملاء في الاستجابة دون تحقق، يستطيع أي أحد يرسل استعلاماً مزيفاً إلى ذلك العنوان أن يأخذ الرد.
{"method": "answerVoiceCallToolQuery", "tool_query_id": "8190448532776044545",
 "result": {"order_id": "A-1042", "status": "قيد الشحن", "eta": "يصل غداً بعد الظهر"}}

مثال — python-telegram-bot

هذا بوت يجيب عن الأداة check_order أعلاه. يستلم عبر getUpdates ويعالج الاستعلامات بالتوازي مع التحديثات الأخرى (concurrent_updates) — فإن تأخر بسبب معالجة تحديث سابق فات budget_ms. ويُبحث عن الطلب بمعرّف مستخدم Pabal الخاص بصاحب الطلب، ولا يُخبر به إلا إذا طابق from.id في الاستعلام.

# order_voice_bot.py — pip install "python-telegram-bot==22.*"
# التشغيل: read -rs BOT_TOKEN && export BOT_TOKEN
#       CUSTOMER_ID=<معرّف مستخدم Pabal لصاحب الطلب> python3 order_voice_bot.py
# عند التجربة ضع معرّفك في CUSTOMER_ID — وهو message.from.id في getUpdates بعد أن ترسل رسالة إلى البوت
import json
import os
import re

from telegram import Update
from telegram.error import TelegramError
from telegram.ext import Application, ContextTypes, TypeHandler

SERVER = "https://pabal.me"
CUSTOMER = int(os.environ.get("CUSTOMER_ID", "0"))
ORDERS = {                                    # جدول صغير للمثال: في الواقع هو نظام الطلبات في المتجر
    (CUSTOMER, "A-1042"): {"status": "قيد الشحن", "eta": "يصل غداً بعد الظهر"},
}


def check_order(arguments, caller_id):
    """{"result": …} أو {"error": …} تُمرَّر كما هي إلى answerVoiceCallToolQuery"""
    order_id = str(json.loads(arguments).get("order_id", "")).strip().upper()
    if not re.fullmatch(r"[A-Z]-\d{4}", order_id):     # الوسائط من صنع النموذج — تحقّق منها مجدداً
        return {"error": "لم أفهم رقم الطلب. مثال: A-1042"}
    found = ORDERS.get((caller_id, order_id))          # طلبات المتصل نفسه فقط
    if found is None:                                  # طلب شخص آخر يُجاب عنه كأنه غير موجود
        return {"error": f"لم أجد الطلب {order_id}."}
    return {"result": json.dumps({"order_id": order_id, **found}, ensure_ascii=False)}


async def on_update(update: Update, context: ContextTypes.DEFAULT_TYPE):
    q = update.api_kwargs.get("voice_call_tool_query")  # الأنواع التي لا يعرفها PTB تأتي في api_kwargs
    if q is None:
        return
    if q["name"] == "check_order":
        answer = check_order(q["arguments"], q["from"]["id"])
    else:
        answer = {"error": "أداة غير معروفة."}
    try:
        await context.bot.do_api_request("answerVoiceCallToolQuery",
                                         api_kwargs={"tool_query_id": q["id"], **answer})
    except TelegramError as e:                         # TOOL_QUERY_EXPIRED: تأخر الرد أو انتهت المكالمة
        print("لم يُقبل الرد:", e, flush=True)


app = (Application.builder().token(os.environ["BOT_TOKEN"])
       .base_url(f"{SERVER}/bot").base_file_url(f"{SERVER}/file/bot")
       .concurrent_updates(True).build())
app.add_handler(TypeHandler(Update, on_update))
app.run_polling(allowed_updates=["message", "voice_call", "voice_call_tool_query"], poll_interval=0, timeout=10)

يتجاهل aiogram أنواع التحديثات التي لا يعرفها، لذا يجب استلام voice_call_tool_query من update.model_extra أولاً في الوسيط الخارجي للموزّع (dp.update.outer_middleware). ويوجد مثال في مستودع Pabal (scripts/bots/voice_tools_aiogram.py)، فاطلبه من مشغّل الخادم إن احتجت إليه.

ما يسمعه النموذج والمتصل

  • النجاح: تصبح النتيجة استجابة الدالة للنموذج (وإن كانت JSON فكقيمة JSON). ويُقرأ النص الذي في النتيجة بوصفه بيانات فقط ولا يصبح تعليمات.
  • الفشل: يسمع النموذج عبارة قصيرة من Pabal بلغة المكالمة (الكورية أو الإنجليزية) ثم يشرح الوضع للمتصل — تجاوز الموعد: «The tool did not answer in time.» (لم ترد الأداة في الوقت المحدد)، وتعذّر الوصول: «The tool could not be reached.» (تعذّر الوصول إلى الأداة)، ووسائط مثل رقم البطاقة: «Pabal does not pass on card numbers or national ID numbers.» (لا يمرّر Pabal أرقام البطاقات ولا أرقام الهوية الوطنية)، أما error من البوت فتُنقل عبارته كما هي. ومع الوسائط الخاطئة يصل النموذج تلميح بمكان الخطأ فيستطيع الاستدعاء مجدداً.
  • أثناء الانتظار تملأ البوابة الصمت: بعد 1.5 ثانية من الصمت تقول «One moment, let me check.» مرة واحدة (وفوراً إذا كانت الأداة بطيئة الرد مؤخراً)، وبعد 5 ثوانٍ تقول «Just a moment longer, please.» مرة واحدة. وتأتي هاتان العبارتان بلغة البوت لا بلغة المتصل، وبالإنجليزية إذا لم يوجد بتلك اللغة تسجيل اكتملت مراجعته (واللغتان اللتان اكتملت مراجعتهما حالياً هما الكورية والإنجليزية).
  • إذا قاطع المتصل الكلام (المقاطعة) فقد تسحب البوابة الاستعلامات المفتوحة، ويكون الرد بعدها TOOL_QUERY_EXPIRED: cancelled.

حدود الأدوات

الاستعلامات المفتوحة في الوقت نفسه: 2 لكل مكالمة، و8 لكل بوت. وعدد الاستدعاءات: 30 في الدقيقة لكل مكالمة، و120 في الدقيقة لكل بوت. وإذا تجاوزتها لا يصل الاستدعاء إلى البوت ويسمع النموذج فوراً too_many_open·rate_limited.

شروط مالكي البوتات

لا يمكن تفعيل نص المحادثة ومفتاح النموذج الخاص بالمالك ووضع الصوت الخام وخوادم MCP والمكالمات من البوتات إلا بعد الموافقة على شروط مالكي البوتات (النسخة 4). والموافقة يقوم بها إنسان في شاشة المكالمات الصوتية لدى @BotFather (من أي شاشة من شاشات نص المحادثة · مفتاح المالك · وضع الصوت الخام · خوادم MCP · المكالمات من البوتات) — ولا يمكن إجراؤها بالرمز المميّز. وحتى لو وافقت على نسخة سابقة فسيُطلب منك ذلك مجدداً. وقبل نشر قسم مالكي البوتات في شروط استخدام الخادم لا يظهر زر الموافقة (والاستدعاء قبل ذلك يعطي 403 VOICE_OWNER_TERMS_NOT_ACCEPTED).

BotFather봇 주인 약관을 확인해 주세요. 대화 기록, 자기 모델 키, 원음 모드, MCP 서버, 봇이 거는 전화에 필요해요. 1. 대화 기록을 켜면, 통화가 끝날 때 AI가 적은 대화 글을 내가 받아요. 받은 글은 내가 처리하고 지켜요. 2. 대화 기록이 켜진 봇에 전화하면, 통화가 연결될 때 안내 음성이 대화 글이 봇 주인에게 간다고 먼저 알려요. 3. 받은 대화 글은 필요한 만큼만 두고, 발신자가 지워 달라고 하면 지워요. 모델 학습에 쓰지 않아요. 4. 자기 모델 키를 쓰면, 통화 소리와 글이 내 키의 제공자에게 내 계약으로 가요. 그 처리의 책임은 나에게 있고, 내 개인정보 안내에 그렇게 적어요. 5. 원음 모드에서는 전화 소리가 파발의 AI 모델이 아니라 내 서버로 가요. 그 소리를 받아 처리하는 건 나예요. 전화를 건 사람의 소리와 말한 내용을 법이 허락하는 범위에서만 쓰고, 필요 없으면 지워요. 6. 내 서버가 어디에 있는지, 무엇을 하는지 전화를 건 사람이 물으면 알려요. 봇 소개에 적어 두면 좋아요. 7. 봇은 언제나 AI 라고 밝혀요. 대답은 AI 가 만들어요. 사람이 직접 대답하거나, 사람의 녹음을 AI 대답처럼 틀지 않아요. 원음 모드에서는 파발이 통화 맨 앞에 "AI 음성 봇입니다. 통화 소리는 봇 개발자의 서버로 갑니다." 안내를 틀어요. 8. 카드 번호·주민등록번호·비밀번호·인증 번호를 묻지 않아요. 음성 봇 규칙도 그대로 지켜요. 9. 파발은 대화 글을 저장하지 않아요. 전해지면, 늦어도 24시간 뒤에는 서버에서 지워져요. 키, 원음 모드와 MCP 서버의 비밀은 암호화해 두고 어디에도 기록하지 않아요. 10. MCP 서버를 등록하면, 통화 중 모델이 그 서버의 도구를 부를 때 발신자가 말한 값이 그 서버로 가요. 그 서버와 그 서버가 넘기는 곳의 처리 책임은 나에게 있고, 받는 곳은 사실대로 신고해요. 11. 약관을 어기면 운영자가 음성 통화나 봇을 멈출 수 있어요. 봇이 거는 전화를 켜려면 아래도 지켜요. 12. 봇이 거는 전화는 선언하고 운영자가 허락한 목적(알림·깨우기·배달·예약)에만 써요. 광고·판매 권유·홍보를 하지 않아요. 배달·예약 안내 안에서도 마찬가지예요. 광고성 정보를 보내면 정보통신망법 제50조를 어긴 것이고 그 책임은 나에게 있어요. 파발은 그런 발신을 곧바로 멈추고 발신을 해지해요. 진료·채용·대출처럼 사람의 권리나 의무에 관한 판단을 이 전화로 알리지 않아요. 13. 전화를 건 봇은 첫 말에서 봇 이름과 AI 라는 것을 밝히고, 사람·기관·파발 직원을 사칭하지 않아요. 14. context 에 건강·금융(금액·계좌)·주소 같은 민감한 정보를 넣지 않고, 비밀번호·인증 번호·카드 번호를 묻거나 권하지 않아요. 15. 만 19세 미만인 사람에게 전화하려 하지 않아요. 16. 조용한 시간·쉬는 중·하루 한도를 피하려 하지 않아요(여러 봇·계정으로 나눠 걸기 포함). 사람이 그만 받겠다고 하면 다시 걸지 않아요. 17. 내 개인정보 처리방침을 두고, 통화 내용이 AI 제공자에게 간다는 것을 알려요. 봇이 거는 전화는 종단 간 암호화가 아니에요. 18. 사람에게 보이는 허락 창에 봇 주인인 내 이름이 나와요. 19. 어기면 그 봇의 발신이 멈추고, 음성 기능 전체가 멈출 수 있어요. 20. 발신으로 받는 사람 정보(파발 id·허락했는지·받았는지·통화 시각과 길이·누가 끊었는지)는 선언한 목적에만 쓰고, 남에게 주지 않고, 허락이 꺼지거나 그 사람이 지워 달라고 하면 지워요. 받은 시각으로 사람의 일과나 있는 곳을 짐작하지 않아요. 21. 허락을 서비스의 조건으로 걸거나 속여서 받지 않아요(허락 링크 메시지 포함). 22. 전화마다 적는 목적은 그 전화의 실제 목적이에요. 깨우기 창을 다른 용도로 쓰지 않아요(서버는 목적을 봇이 적은 대로 믿어요). 23. 봇 토큰을 지키고, 샌 것을 알면 곧바로 @BotFather 에서 재발급해요. 토큰이 곧 사람에게 전화할 권한이에요. 24. 봇이 건 전화에는 대화 기록(1항)이 오지 않아요. 봇에게 가는 것은 20항의 것이에요.
약관에 동의하기

يرد BotFather باللغة الكورية. ومضمون الشروط أعلاه كما يلي، وتوافق عليها بزر الموافقة على الشروط (약관에 동의하기):

  1. إذا فعّلت نص المحادثة أستلم عند انتهاء المكالمة نص المحادثة الذي كتبه الذكاء الاصطناعي، وأتولى أنا معالجة النص الذي أستلمه وحمايته.
  2. عند الاتصال ببوت فُعّل فيه نص المحادثة، يُعلن صوتٌ عند اتصال المكالمة أولاً أن نص المحادثة سيذهب إلى مالك البوت.
  3. لا أحتفظ بنص المحادثة المستلم إلا بقدر الحاجة، وأحذفه إذا طلب المتصل ذلك، ولا أستخدمه لتدريب النماذج.
  4. إذا استخدمت مفتاح نموذجي الخاص يذهب صوت المكالمة ونصها إلى مزوّد مفتاحي بموجب عقدي. وتقع عليّ مسؤولية تلك المعالجة، وأذكر ذلك في إشعار الخصوصية الخاص بي.
  5. في وضع الصوت الخام يذهب صوت المكالمة إلى خادمي لا إلى نموذج الذكاء الاصطناعي في Pabal، وأنا من يستلم ذلك الصوت ويعالجه. ولا أستخدم صوت المتصل وما قاله إلا في الحدود التي يسمح بها القانون، وأحذفه حين لا أحتاج إليه.
  6. إذا سأل المتصل أين يقع خادمي وماذا يفعل فإنني أخبره. ويحسن أن أكتب ذلك في تعريف البوت.
  7. يفصح البوت دائماً عن أنه ذكاء اصطناعي، والردود يولّدها الذكاء الاصطناعي. ولا يرد إنسان بنفسه، ولا تُشغَّل تسجيلات بشرية على أنها ردود للذكاء الاصطناعي. وفي وضع الصوت الخام يشغّل Pabal في بداية المكالمة الإعلان «This is an AI voice bot. The call's audio goes to the bot developer's server.»
  8. لا أطلب أرقام البطاقات ولا أرقام الهوية الوطنية ولا كلمات المرور ولا رموز التحقق، وألتزم بقواعد البوتات الصوتية كما هي.
  9. لا يخزّن Pabal نص المحادثة، وبعد تسليمه يُحذف من الخادم خلال 24 ساعة على الأكثر. وتُحفظ المفاتيح وأسرار وضع الصوت الخام وخوادم MCP مشفّرة ولا تُسجَّل في أي مكان.
  10. إذا سجّلت خادم MCP، فعندما يستدعي النموذج أثناء المكالمة أداة من أدوات ذلك الخادم تذهب القيم التي قالها المتصل إلى ذلك الخادم. وتقع عليّ مسؤولية المعالجة لدى ذلك الخادم ولدى الجهات التي يحوّل إليها، وأصرّح بجهة الاستلام بصدق.
  11. إذا خُرقت الشروط يمكن للمشغّل إيقاف المكالمات الصوتية أو البوت.

البنود 12–24 هي ما تلتزم به عند تفعيل المكالمات من البوتات: استخدامها فقط للأغراض المعلنة التي أذِن بها المشغّل، دون إعلان؛ وذكر اسم البوت وأنه ذكاء اصطناعي في كلماته الأولى؛ وعدم وضع معلومات حساسة في context؛ وعدم محاولة الاتصال أبداً بمن هم دون 19 عاماً؛ وعدم الالتفاف على ساعات الهدوء أو التوقف المؤقت أو الحدود؛ واستخدام ما تعرفه عن المتلقّين لتلك الأغراض فقط وحذفه عند إيقاف الإذن؛ وحماية الرمز المميّز. والمكالمات التي يجريها البوت لا تأتي بنص محادثة (البند 24). وقد تتغير صياغة الشروط بعد المراجعة القانونية.

خوادم MCP

خوادم MCP معطّلة افتراضياً، وهي معطّلة أيضاً على pabal.me

لا تعمل إلا بعد أن يجهّز المشغّل مكوّنات MCP (pabal-mcp، والموقِّع، ووكيل الخروج) وينشر قسم bot-calls-mcp في سياسة الخصوصية وقسم مالكي البوتات في شروط الاستخدام (وتخصيص عنوان خروج خاص لطلبات MCP شرط تشغيلي لا تتحقق منه الشيفرة). وحتى بعد التفعيل، ما لم يعرض التطبيق جهة استلام MCP الخاصة بالبوت في نافذة التأكيد فلا يمكن إلا التسجيل، ولا تحمل أي مكالمة أدوات MCP (mcp_unavailable_reason: "sheet"). وعلى خادم عُطّلت فيه MCP يعطي setVoiceCallMcpServer النتيجة 403 VOICE_NOT_AVAILABLE: MCP tools are not available on this server، ولا يوجد في @BotFather زر «خوادم MCP» (MCP 서버).

عندما تسجّل خادم MCP يشغّله مالك البوت (أو تعاقد عليه)، يستطيع النموذج استدعاء أدوات ذلك الخادم أثناء المكالمة. والمستدعي ليس شيفرة البوت بل Pabal — فعميل MCP في Pabal يرسل الطلبات إلى ذلك الخادم عبر وكيل الخروج في Pabal ويلتزم بالمهلة · الحجم · الفحص. لذلك تذهب القيم التي يقولها المتصل إلى ذلك الخادم (البند 10 من شروط مالكي البوتات).

ما يجب أن يتوفر في خادم MCP

  • Streamable HTTP، POST (نسخة البروتوكول 2024-11-05 فأحدث). شهادة عامة لـ https://، بلا إعادة توجيه، والمنافذ التي يسمح بها الخادم فقط (على pabal.me المنفذ 443).
  • الخادم الذي يستخدم بيانات اعتماد يجب أن يرفض بصدق بـ401 عند غيابها، وألّا يقبل بيانات اعتماد خاطئة. وبيانات الاعتماد الواحدة لا يستخدمها إلا بوت واحد.
  • وثيقة المشغّل: يضع مشغّل الخادم وثيقة JSON (لا تتجاوز 32KB، وبلا إعادة توجيه) في https://<host>/.well-known/pabal-voice-mcp — الجهة المشغّلة · البلد · وسيلة الاتصال، والغرض، وأنواع البيانات وموقعها، وجهات الاستلام والمعالجة من الباطن، ومدة الاحتفاظ. وهذه معلومات جهة الاستلام التي تظهر في نافذة التأكيد لدى المتصل، وإن لم توجد يصرّح بها مالك البوت عند التسجيل (أدناه). أما الخادم العام الذي يستطيع أي أحد استدعاءه بلا بيانات اعتماد (auth.type: "none") فيجب أن يكتب "accepts": "any" في بند نقطة النهاية في هذه الوثيقة ليعلن أنه يقبل استدعاءات Pabal.
  • ما يضيفه Pabal: في _meta الخاص بـ tools/call تُضاف المهلة المتبقية me.pabal/budget_ms (بالمللي ثانية) وحد حجم النتيجة me.pabal/result_budget_bytes، وقد يُضاف توكيد موقّع من Pabal (me.pabal/assertion، JWT). والجهة المُصدِرة للتوكيد وsub (bot:<bot id>) ومفاتيح التحقق (JWKS) يخبرك بها getVoiceCallMcpBinding.
  • tools/list لا يتجاوز 2MB · و20 صفحة. ولا يجوز أن تكون فيه خطوات تتطلب إنساناً (مثل تسجيل الدخول عبر المتصفح).
  • تعطي كل أداة تصنيفها عبر _meta["me.pabal/voice"] — data (none·caller·third_party)، content (system·third_party_text)، effect (none·write·notify·financial)، max_ms (لا يتجاوز 15,000)، max_result_bytes (لا يتجاوز 65,536)، واختيارياً p95_ms. وإن لم يقدّمه الخادم فإن مالك البوت يحدده عبر tools عند التسجيل.
  • المواصفة 1.0 لا تعطي إلا L1: لا تُثبَّت في المكالمة إلا الأدوات ذات data: "none" وeffect: "none" (أي الأدوات التي تقرأ معلومات عامة فقط). أما الأدوات التي تتعامل مع بيانات المتصل (L2) أو تنفّذ شيئاً (L3) فتُسجَّل فقط ولا تُستخدم.
  • عند التسجيل يجري Pabal اختبارات المطابقة (العنوان · TLS · النسخة · 401 · قائمة الأدوات · بنية الأدوات · نطاق بيانات الاعتماد · الخطوات البشرية). وإن فشلت يُرفض التسجيل بـMCP_CONFORMANCE_FAILED: CT-xx.

التسجيل

تُرسل الأسرار في جسم الطلب فقط (وفي سلسلة الاستعلام 400). ضعها في ملف ثم أرسلها كي لا يبقى السرّ في سطر الأوامر.

umask 077
cat > mcp-shop.json <<'EOF'
{"label": "shop",
 "url": "https://mcp.example.com/mcp",
 "auth": {"type": "bearer", "token": "(الرمز المميّز الذي أعطاه خادم MCP)"}}
EOF
curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallMcpServer" -H 'Content-Type: application/json' -d @mcp-shop.json
  • label: [a-z][a-z0-9]{0,11}، ولا يبدأ بـ pabal. ولكل بوت خادمان. واسم الأداة الذي يراه النموذج هو label__tool_name.
  • auth.type: none، bearer (token)، headers (حتى 3 ترويسات)، client_credentials (token_url، client_id، client_secret، أو private_key_jwt: true — بعد استدعاء createVoiceCallMcpClientKey أولاً). ولا يوجد authorization_code بعد. وnone (خادم عام) لا يُسجَّل إلا إذا أعلنت الوثيقة العامة لمشغّل ذلك الخادم أنه يقبل استدعاءات Pabal (وإلا MCP_OPT_IN_REQUIRED).
  • التغييرات التي تغيّر جهة الاستلام (خادم جديد، أو عنوان أو مضيف رمز مميّز مختلف، أو تصريح جديد، أو مستوى أعلى) يأتي ردها 400 MCP_CHANGE_NEEDS_CONFIRMATION، ولا تُطبَّق إلا إذا ضغط مالك البوت خلال 5 دقائق زر التأكيد (확인 — 이 변경을 적용) الذي يظهر في @BotFather. والغرض ألّا يذهب كلام المتصل خفيةً إلى مكان آخر حتى لو تسرّب الرمز المميّز. أما تدوير الأسرار وحدها فيسري فوراً.
  • إذا لم تكن لدى مشغّل الخادم وثيقة عامة، يصرّح مالك البوت بصدق عبر declaration بجهة الاستلام (الجهة المشغّلة · البلد · وسيلة الاتصال · الغرض · أنواع البيانات · الموقع · المعالجة من الباطن · مدة الاحتفاظ). ويرى المتصل ذلك في نافذة التأكيد، وإذا تغيّر يُسأل المتصل مجدداً.
  • التسجيل · الفحص: 10 مرات في الساعة لكل بوت، و20 لكل مالك. والطرق الأخرى: refreshVoiceCallMcpServer (إعادة الفحص)، وdeleteVoiceCallMcpServer (ممكن دائماً)، وgetVoiceCallMcpBinding (الجهة المُصدِرة · sub · JWKS التي يستخدمها خادمك للتعرّف على استدعاءات Pabal)، وcreateVoiceCallMcpClientKey.
  • حالة الخوادم والأدوات المسجّلة تظهر في getVoiceCallSettings.mcp_servers (ولا تظهر المسارات · الاستعلامات · الأسرار). والنص الكامل للمواصفة (Pabal Voice MCP Profile 1.0) في docs/app/voice-bot-mcp-profile.html داخل مستودع Pabal، فاطلبه من مشغّل الخادم إن احتجت إليه.

مفتاح النموذج الخاص بالمالك

لا يمكن بعد استخدام مفتاح المالك على أي خادم إنتاج

المكالمات التي تُستقبل بمفتاح المالك تتطلب أن يرى المتصل نافذة تأكيد (النسخة 2) تقول إن الصوت يذهب إلى مزوّد ذلك المفتاح، ولا يوجد بعد تطبيق يعرض هذه النافذة. لذلك لا تقبل الخوادم غير الاختبارية المفاتيح أصلاً حتى يفعّل المشغّل علامة جاهزية التطبيقات (403 VOICE_OWN_KEY_NOT_AVAILABLE). ويجب أيضاً أن يكون على الخادم مفتاح رئيسي.

تشغّل مكالمات البوت بحساب النموذج الخاص بمالك البوت نفسه. وتُحتسب التكلفة على حساب المزوّد الخاص بالمالك، ولا تستهلك مكالمات مفتاح المالك الحدود اليومية للبوت · المالك · الخادم على المنصة (وتبقى حدود المتصل). ويجب الموافقة على شروط مالكي البوتات أولاً.

curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallApiKey" -d provider=google \
  --data-urlencode "api_key@vertex-sa.json"
  • provider: google هو مفتاح JSON لحساب خدمة Vertex AI (ولا يُحفظ منه إلا الحقول اللازمة)، وopenai مفتاح API يبدأ بـ sk-. وأي قيمة أخرى تعطي VOICE_PROVIDER_INVALID. ويُرسل المفتاح في جسم الطلب فقط (وفي سلسلة الاستعلام 400) — وإذا قرأته من ملف كما في المثال فلن يبقى في سطر الأوامر. ولا تلصقه في المحادثة مع @BotFather.
  • قبل قبول المفتاح يتحقق الخادم عبر جلسة اختبار في البوابة من أنه يفتح فعلاً (حتى 12 ثانية). فإن نجح حفظه وأعاد True. وإن رفضه المزوّد API_KEY_INVALID، وإن أمكن تسجيل الدخول لكن بلا صلاحية لواجهة الوقت الحقيقي (Realtime API) API_KEY_NO_REALTIME_ACCESS، وإن لم توجد بوابة للاختبار أو كانت مشغولة 503 API_KEY_CHECK_UNAVAILABLE: …: the key was not kept.
  • الاختبار: 5 مرات في الساعة لكل بوت، و10 لكل مالك (لكل البوتات معاً). ولا يُحتسب إلا الاختبار الذي وصل إلى البوابة.
  • لا يظهر المفتاح في أي مكان — لا في الردود ولا الأخطاء ولا السجلات ولا الشاشات. وفي getVoiceCallSettings.api_key لا يوجد إلا المزوّد · تاريخ الضبط · البصمة (8 خانات) · وهل يمكن استخدامه.
  • إذا تعذّر استخدام المفتاح المضبوط (تغيّرت الشروط، أو لم يستطع الخادم فتح المفتاح، ونحو ذلك) فإن البوت لا يستقبل المكالمات — ولا يُستبدل به مفتاح المنصة خفيةً. وإذا حذفت المفتاح بـdeleteVoiceCallApiKey (ممكن دائماً) يستقبل البوت بمفتاح المنصة، وتنتهي المكالمات الجارية بذلك المفتاح.
  • لا حد يومي من Pabal على مكالمات مفتاح المالك — لا يسري إلا طول المكالمة الواحدة والمكالمات المتزامنة وحدود المتصل. وبما أن التكلفة على حسابك فاضبط تنبيهات الميزانية في لوحة المزوّد.
  • مفتاح OpenAI لا يُستخدم إلا إذا فعّله مشغّل البوابة بشكل منفصل (والافتراضي في البوابة مفاتيح Google فقط). وإن لم توجد بوابة مفعّلة يكون اختبار مفتاح OpenAI 503 … no_gateway. ولم يُختبر مسار مفتاح OpenAI مع OpenAI الحقيقي بعد (بل مع خوادم وهمية فقط)، والمكالمات التي تُستقبل بمفتاح OpenAI تستخدم صوت OpenAI الذي تحدده البوابة (الافتراضي marin) بغض النظر عن إعداد صوت البوت. ويُستخدم مفتاح Google في منطقة Vertex الخاصة بالبوابة (us-central1).
  • في وضع الصوت الخام يُحفظ المفتاح فقط ولا يُستخدم.

نص المحادثة

لا يصل نص المحادثة إلا من خادم سمح به المشغّل

يجب أن يُتم المشغّل مراجعة قانونية ثم يسمح به في صفحة الإدارة (معطّل افتراضياً). وعلى خادم لم يسمح به، حتى لو فعّلت الخيار يبقى transcript_available: false وtranscript_unavailable_reason: "not_allowed" ولا يصل أي نص محادثة.

عند التفعيل يستلم مالك البوت نص محادثة المكالمة الذي كتبه الذكاء الاصطناعي. وافق على شروط مالكي البوتات ثم فعّل الخيار.

نص المحادثة والاحتفاظ بالمحادثة أمران مختلفان

نص المحادثة (هذا القسم) يفعّله مالك البوت ويستلمه مالك البوت، ويسمع المتصل إعلاناً صوتياً عند الاتصال. أما الاحتفاظ بالمحادثة فيختاره المتصل بخانة الاختيار في نافذة التأكيد، ويبقى النص بعد انتهاء المكالمة في محادثته هو فقط — لا يصل إلى البوت ولا إلى مالك البوت، ولا إعلان صوتياً له. ويمكن أن تجمع المكالمة الواحدة الأمرين. وليس للاحتفاظ بالمحادثة مفتاح يفعّله مالك البوت، بل يُفعَّل إذا كان في سياسة الخصوصية للخادم قسم bot-calls-keep-text (قيد الإعداد — استخدام التطبيق — البوتات الصوتية).

curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallSettings" -d transcript=true
  • المكالمات التي يجريها البوت ليس لها نص محادثة (المكالمات من البوتات) — حتى لو كان المفتاح مفعّلاً.
  • عند الاتصال ببوت فُعّل فيه نص المحادثة يسمع المتصل مباشرة بعد إعلان الذكاء الاصطناعي: «What is said on this call is written down and sent to the bot's owner.» (يُكتب ما يُقال في هذه المكالمة ويُرسل إلى مالك البوت). ولا يُكتب إلا ما يُقال بعد انتهاء الإعلان. ولا يوجد نص محادثة لمكالمات وضع الصوت الخام.
  • يأتي transcript ضمن voice_call الخاص بالمكالمة المنتهية (event: "ended"). وهو النسخة الكاملة الوحيدة، فضع voice_call في allowed_updates. وإن أردت استلامه دوراً دوراً أثناء المكالمة فأضف voice_call_transcript أيضاً (عبر HTTP Bot API فقط، ولا يصل عبر MTProto).
  • الأدوار التي تتضمن رقم بطاقة أو رقم هوية وطنية تُرسل بعد استبدال كل الأرقام بـ *. أما الأرقام المقسّمة على دورين أو المنطوقة بالكلمات فلا تُلتقط، فتعامل بحذر مع النص الذي تستلمه أيضاً.
  • الدور الواحد 4,096 بايت، والمكالمة الواحدة 256KiB · و4,000 دور. وما يتجاوز ذلك يُهمل ويكون truncated: true.
  • لا يخزّن Pabal نص المحادثة: يبقى في الذاكرة طوال المكالمة، ويُسلَّم إلى البوت مرة واحدة، وما لم يُؤخذ يُحذف بعد 24 ساعة. ويضيع عند إعادة تشغيل الخادم. والاحتفاظ به ومعالجته بعد الاستلام مسؤولية مالك البوت.
"transcript": {
  "turns": [
    {"role": "bot", "text": "معك Order Helper. كيف يمكنني مساعدتك؟", "offset_ms": 5200},
    {"role": "caller", "text": "أين وصل الطلب A-1042؟", "offset_ms": 9800}
  ],
  "truncated": false
}
{"update_id": 21, "voice_call_transcript": {"voice_call_id": "4611686018427387905", "seq": 2,
 "role": "caller", "text": "أين وصل الطلب A-1042؟", "is_final": true, "offset_ms": 9800, "date": 1790380813}}

role هو caller (المتصل) أو bot (النموذج)، وoffset_ms عدد المللي ثانية منذ بدء صوت المكالمة.

وضع الصوت الخام

لا يمكن استخدام وضع الصوت الخام بعد

لا يستطيع المتصل الاتصال ببوت صوت خام إلا إذا عرض التطبيق نافذة تأكيد الصوت الخام التي تقول «يذهب صوت المكالمة إلى خادم مطوّر البوت»، ولا يوجد بعد إصدار من التطبيق يتضمن تلك النافذة. ويجب أن يتوفر على الخادم أيضاً مفتاح رئيسي، وقسم bot-calls-stream في سياسة الخصوصية، وقسم مالكي البوتات، وبوابة تستقبل الصوت الخام. وإن لم تتوفر يعطي mode=stream النتيجة 403 VOICE_STREAM_NOT_AVAILABLE.

في وضع الصوت الخام يذهب صوت المكالمة إلى خادم مالك البوت لا إلى نموذج الذكاء الاصطناعي في Pabal. يسمع خادمك الصوت ويعيد الرد صوتاً — وأي ذكاء اصطناعي تستخدم فهذا قرارك (نموذج وقت حقيقي من مزوّد آخر، أو تركيبة خاصة بك من التعرّف على الكلام وتوليفه). ويظل Pabal يشغّل إعلان الذكاء الاصطناعي في البداية، ويلتزم كما هو بتأكيد المتصل والحدود وطول المكالمة وحد المكالمات المتزامنة. ولا حاجة إلى تعليمات، أما الأدوات · مفتاح المالك · نص المحادثة فتُحفظ فقط ولا تُستخدم (stream_ignores).

المتصل ⁧Pabal (الخادم · البوابة)⁩ ⁧خادم wss لمالك البوت⁩ ⁧① نافذة تأكيد الصوت الخام · اتصال⁩ ⁧② ترقية + توقيع · pabal.voice.v1⁩ ③ start {call_id, bot_id, format} ⁧④ ready (خلال 3 ثوانٍ)⁩ إعلان الذكاء الاصطناعي · خلاله لا يذهب صوت المتصل إلى خادم البوت ⑤ live الصوت (عبر الترحيل) ⁧⑥ PCM16 24 kHz · قطع ⁦20 ms⁩⁩ ⑦ clear · mark · hangup ⁧⑧ stop {reason} ← إغلاق 1000⁩
وضع الصوت الخام: خادم مالك البوت في مكان النموذج

شرح المخطط

  • ثلاثة أعمدة: على اليسار المتصل، وفي الوسط Pabal (الخادم وبوابة الصوت)، وعلى اليمين خادم مالك البوت. لا يتحدث خادم البوت مع التطبيق ولا مع خادم Pabal مباشرة، بل مع البوابة وحدها عبر WebSocket.
  • ②–④ تحدث أثناء رنين الهاتف: تتصل البوابة عبر TLS بالعنوان العام الذي تحقّقت منه، وتُجري الترقية مع ترويسة التوقيع، ثم ترسل start. ويجب أن يرسل خادم البوت ready خلال 3 ثوانٍ كي تُستقبل المكالمة، وإلا يسمع المتصل إشارة انشغال.
  • الخانة الرمادية بوابة عبور: قبل أن ينتهي إعلان الذكاء الاصطناعي («This is an AI voice bot. The call's audio goes to the bot developer's server.») لا يذهب صوت المتصل إلى خادم البوت، ولا يُشغَّل الصوت الذي يرسله البوت قبل ذلك. وفي لحظة انتهائه يُرسل ⑤ live.
  • ⑥ الصوت في الاتجاهين PCM16 · 24 kHz · أحادي القناة، ومن جهة المتصل دائماً قطع طولها 20 ms (960 بايت). والسهم الأزرق هو ما يرسله Pabal، والرمادي ما يرسله خادم البوت.
  • ⑦ و⑧: يستطيع البوت إرسال المقاطعة (clear) وطلب إشعار بموضع التشغيل (mark) والإنهاء (hangup)، وعند انتهاء المكالمة يرسل Pabal stop مع السبب ثم يغلق بالرمز 1000.

الإعداد والفحص التجريبي

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

بعد الموافقة على شروط مالكي البوتات (@BotFather ← المكالمات الصوتية (음성 통화) ← وضع الصوت الخام (원음 모드) ← الموافقة على شروط مالكي البوتات (봇 주인 약관에 동의))، أبلغ عنوان الخادم والسرّ. والسرّ من 32 إلى 128 حرف ASCII قابل للطباعة (بلا مسافات)، والعنوان الجديد يُرسل دائماً مع سرّ جديد.

umask 077
openssl rand -hex 32 | tr -d '\n' > stream-secret.txt      # يجب ألّا ينتهي الملف بسطر جديد
curl -s "https://pabal.me/bot$BOT_TOKEN/setVoiceCallSettings" -d mode=stream \
  -d stream_url=wss://bot.example.com:8443/voice --data-urlencode "stream_secret@stream-secret.txt"
  1. صيغة العنوان: wss:// فقط، بلا اسم مستخدم · # · مسافات، وبأحرف ASCII فقط (وغيرها يُرمَّز بالنسبة المئوية)، ولا يتجاوز 2,048 حرفاً. والمضيف اسم فيه نقطة أو IP عام (localhost غير مقبول)، والمنفذ 443 أو من 1024 إلى 65535. وإلا 400 STREAM_URL_INVALID: ….
  2. حدود الفحص التجريبي تُحتسب لحظة الطلب: 5 مرات في الساعة لكل بوت، و10 لكل مالك، وفي الوقت نفسه 1 لكل بوت · و2 لكل مالك. وإذا تجاوزتها 429. أما الفحص الذي لم يتصل بأي مكان لأن البوابة مشغولة فلا يُحتسب.
  3. التحقق من العنوان: إذا أشار الاسم ولو إلى عنوان واحد خاص أو محجوز أو إلى عنوان Pabal نفسه فالنتيجة 400 STREAM_URL_INVALID: the host must resolve only to public addresses.
  4. الفحص التجريبي: تحاول البوابة الاتصال مرة واحدة ("probe": true في start). فإن وصل ready يُفعَّل الوضع ويُحفظ السرّ مشفّراً تحت المفتاح الرئيسي للخادم. وإن فشل 400 STREAM_PROBE_FAILED: <code> — unreachable (أحد: الاسم · الاتصال · الوقت · TLS)، وhandshake_failed (رد غير 101 — عادةً رفض التوقيع)، وredirect، وno_subprotocol، وno_ready، وbad_ready، وaddress_refused، وbusy، وurl وغيرها — والرموز الأخرى تأتي كما أعطتها البوابة. وينتظر الخادم حتى 12 ثانية.
  5. إذا فُعّل وضع الصوت الخام أو تغيّر العنوان عبر Bot API (بالرمز المميّز) يرسل @BotFather إلى مالك البوت «تغيّر عنوان الصوت الخام إلى … (السرّ …)» (원음 주소가 … 로 바뀌었어요 (비밀 …)) مع زر العودة إلى الوضع المُدار (관리형으로 돌아가기)، وعند العودة إلى الوضع المُدار يرسل إشعاراً بذلك مع زر المكالمات الصوتية (음성 통화). والغرض منع ذهاب الصوت خفيةً إلى خادم آخر إذا تسرّب الرمز المميّز.
  • إذا وضعت stream_secret في سلسلة الاستعلام فالنتيجة 400 قبل أي شيء آخر. ولا يمكن ضبط وضع الصوت الخام عبر استجابة webhook.
  • للعودة إلى الوضع المُدار استخدم mode=managed (يحذف العنوان والسرّ). وإذا كان مفعّلاً والتعليمات فارغة فالنتيجة VOICE_INSTRUCTIONS_REQUIRED، فأرسل التعليمات معه.
  • على الخادم الذي لا يستقبل إلا البوتات التي سمح بها المشغّل، يجب أن يراجع المشغّل عنوان الصوت الخام ويسمح مجدداً كي يستقبل البوت المكالمات.
  • لا يظهر في getVoiceCallSettings إلا stream_url وبصمة السرّ. وفي مكالمات الصوت الخام لا يعمل sendVoiceCallContext (VOICE_CONTEXT_NOT_SUPPORTED)، وينهي endVoiceCall المكالمة فوراً دون وداع.

المواصفة

  • TLS 1.2 فأعلى، والشهادة شهادة عامة تطابق الاسم الوارد في URL. تتصل البوابة بالـ IP الذي تحقّقت منه، لكن SNI وفحص الشهادة يتمان بالاسم. ولا تتبع إعادة التوجيه (3xx).
  • يجب اختيار البروتوكول الفرعي pabal.voice.v1 (Sec-WebSocket-Protocol). بلا ضغط. والرسالة الواحدة حتى 65,536 بايت، والرسالة النصية حتى 4,096 بايت (UTF-8).
  • ترسل البوابة ping كل 15 ثانية. وإذا لم يصل pong خلال 15 ثانية تقطع الاتصال (ومعظم المكتبات ترد تلقائياً).
  • الرسائل النصية أحداث JSON، والرسائل الثنائية صوت. وصيغة الصوت واحدة: PCM16 little-endian، أحادي القناة، 24,000 Hz. وcall_id·bot_id سلاسل أعداد عشرية.
الاتجاهالرسالةمتى · المعنى
Pabal ← البوت{"event":"start","protocol":1,"call_id":"…","bot_id":"…","probe":false,"caller":{"lang":"ko"},"format":{"encoding":"pcm_s16le","sample_rate":24000,"channels":1,"frame_ms":20},"max_seconds":300}مرة واحدة بعد الترقية مباشرة. لا يتضمن اسم المتصل · رقمه · معرّفه (بل caller.lang فقط — ومعلومات الشخص تأتي عبر التحديث voice_call)
Pabal ← البوت{"event":"live"}انتهى إعلان الذكاء الاصطناعي. ومن هنا يتدفق الصوت
Pabal ← البوتثنائيةصوت المتصل، دائماً 960 بايت (20 ms). بعد live فقط
Pabal ← البوت{"event":"mark","name":"…"}انتهى تشغيل الصوت الذي يسبق الـ mark الذي أرسله البوت
Pabal ← البوت{"event":"cleared","played_ms":N,"dropped_ms":N,"marks":["…"]}الرد على clear
Pabal ← البوت{"event":"buffer_full","dropped_ms":N}تراكم صوت البوت أكثر من 30 ثانية فأُهمل الفائض
Pabal ← البوت{"event":"stop","reason":"…","played_ms":N,"duration_ms":N}انتهت المكالمة. وسيُغلق قريباً بالرمز 1000
البوت ← Pabal{"event":"ready"}بعد start خلال 3 ثوانٍ، وقبل أي شيء
البوت ← Pabalثنائيةصوت البوت: عدد زوجي من البايتات، من 2 إلى 65,536 بايت. وما يُرسل قبل live يُهمل
البوت ← Pabal{"event":"clear"}المقاطعة: أوقف الآن صوت البوت المنتظر في الطابور
البوت ← Pabal{"event":"mark","name":"[A-Za-z0-9_.:-]{1,64}"}«أخبرني عندما يصل التشغيل إلى هنا». حتى 256 في الانتظار لكل مكالمة
البوت ← Pabal{"event":"hangup"}إنهاء المكالمة. يُشغَّل الصوت المنتظر حتى 5 ثوانٍ أولاً ثم تُنهى
  • قيم event غير المعروفة تُتجاهل. أما النص الذي ليس JSON، والنص الذي يتجاوز 4KiB، والصوت ذو العدد الفردي من البايتات أو الذي يتجاوز 64KiB فهي خرق للمواصفة وتُنهي المكالمة (ويسمع المتصل اعتذاراً).
  • الحد الأعلى للإرسال: أحداث نصية بمعدل 50 في الثانية (وبحد لحظي 100)، وصوت بمعدل 4 أضعاف الزمن الحقيقي (وبحد لحظي 60 ثانية). وإذا لم يقرأ مقبس البوت فتراكم صوت المتصل أكثر من 30 ثانية (1,500 قطعة) تنتهي المكالمة أيضاً.

التوقيع

تُرفق البوابة هذه الترويسة بطلب الترقية.

X-Pabal-Voice-Signature: t=<ثوانٍ Unix>,nonce=<16 بايت hex>,call=<call_id>,v1=<hex>
v1 = HMAC-SHA256(stream_secret, "pabal-voice-stream/1\n<t>\n<nonce>\n<call>\n<host>\n<path?query>")
  • host هو مضيف URL الذي أبلغت به Pabal (بأحرف صغيرة، وبلا منفذ). ولا يجوز حسابه من ترويسة Host في الطلب — فتلك القيمة يكتبها المرسل ولا تربط التوقيع بالمضيف. وpath?query هو مسار URL واستعلامه كما هما (وإن لم يوجدا فـ /). وفي الفحص التجريبي يكون call رقم الفحص.
  • يجب على خادم البوت حتماً أن يتحقق من: |الآن − t| ≤ 300 ثانية، واستخدام nonce نفسه مرة واحدة فقط، ومقارنة HMAC بايتاً ببايت بدالة ثابتة الزمن. وإن كان خاطئاً فليرد برد غير 101 مثل 401.
  • ثم يتحقق من مطابقة start.call_id لقيمة call في التوقيع. وإن اختلفا يغلق بالرمز 1008 — كي لا يُعاد استخدام ترقية موقّعة لمكالمة أخرى. وإن كان الخادم يخدم بوتاً واحداً فليتحقق أيضاً من أن start.bot_id هو بوته.
  • قيم الاختبار: السرّ example-stream-secret-of-the-bot-developer-0001، وt=1790000000، وnonce 00112233445566778899aabbccddeeff، وcall 4611686018427387905، وhost bot.example.com، والمسار /voice?bot=100042 ← v1=807517ff96ac75223bde35b48c775f49a2eda2fb4832a02c0ea832b914d70d0c.

الصوت · المقاطعة · mark · الإنهاء

  • المخزن المؤقت من البوت إلى المتصل: تُخرج البوابة الصوت بسرعة التشغيل، فيمكنك الإرسال دفعة واحدة، لكن إذا تراكم أكثر من 30 ثانية يُهمل الفائض وتُرسل buffer_full. وإذا انقطع صوت البوت 300 ms يُعدّ «مقطع» قد انتهى (وهو وحدة mark·clear).
  • clear: يُهمل كل صوت البوت الذي لم يخرج بعد، ويسود الصمت ابتداءً من الـ 10 ms التالية (القيمة المقيسة أقل من 50 ms). وفي الرد cleared تكون played_ms مجموع صوت البوت الذي شُغّل فعلاً في هذه المكالمة حتى الآن، وdropped_ms ما أُهمل هذه المرة، وmarks أسماء علامات mark المعلّقة بالمقاطع المهملة — وتستخدمها لإخبار النموذج «أين انقطع الكلام».
  • mark: عندما ينتهي تشغيل الصوت الذي يسبقها تعود بالاسم نفسه. ويحسن أن ينتهي الصوت الذي يسبق mark عند مضاعف لـ 480 بايت (10 ms) (وتُملأ القطعة المتبقية بالصمت).
  • الإنهاء: يرسل البوت hangup أو يغلق إغلاقاً نظيفاً بالرمز 1000·1001. وأي إغلاق آخر أو انقطاع TCP يُعامل كخرق للمواصفة (ويسمع المتصل اعتذاراً). وعندما ينهي Pabal المكالمة يكون stop.reason رمزاً قصيراً — hangup (أنهاها المتصل)، max_length، bot_ended، disconnect، drain، kill_switch، operator، link_lost، media_failed … وعامل القيم غير المعروفة على أنها «انتهت».
  • في الفحص التجريبي يكون في start: "probe": true، وcaller فارغ، وmax_seconds: 0. وعندما ترسل ready يصل {"event":"stop","reason":"probe"} ويُغلق الاتصال. ولا تفتح في الفحص التجريبي ما له تكلفة مثل جلسات النماذج.

مثال — أصغر خادم للصوت الخام

هذا خادم يتحقق من المواصفة كلها دون ذكاء اصطناعي: يشغّل رنة عند live، ثم يعيد صوت المتصل كما هو (صدى). ويتحقق من التوقيع · الوقت · nonce · المكالمة · البوت جميعاً. استخدمه أولاً لترى المكالمة تعمل حتى النهاية، ثم ضع ذكاءك الاصطناعي في مكان الصدى. وفي مستودع Pabal أيضاً scripts/bots/stream_echo_bot.py (مع عرض للمقاطعة) وscripts/bots/stream_openai_relay.mjs (يرحّل إلى Realtime بمفتاح OpenAI الخاص بك، مثال لم يُشغَّل)، فاطلبهما من مشغّل الخادم إن احتجت إليهما.

# stream_bot.py — pip install "websockets>=13"
# التشغيل: STREAM_SECRET_FILE=stream-secret.txt TLS_CERT=fullchain.pem TLS_KEY=privkey.pem \
#       PUBLIC_HOST=bot.example.com BOT_ID=<معرّف البوت — الرقم قبل : في الرمز المميّز> python3 stream_bot.py
import asyncio
import hashlib
import hmac
import json
import math
import os
import ssl
import struct
import time

from websockets.asyncio.server import serve

SECRET = open(os.environ["STREAM_SECRET_FILE"], "rb").read().strip()
PUBLIC_HOST = os.environ["PUBLIC_HOST"].lower()   # مضيف stream_url الذي أبلغت به Pabal (وليس ترويسة Host)
BOT_ID = os.environ.get("BOT_ID", "")             # إذا تُرك فارغاً يقبل مكالمات أي بوت
PATH = os.environ.get("STREAM_PATH", "/voice")
PORT = int(os.environ.get("PORT", "8443"))
CHIME = b"".join(struct.pack("<h", int(8000 * math.sin(2 * math.pi * 880 * i / 24000)))
                 for i in range(24000 * 6 // 10))  # ‏880 Hz لمدة 0.6 ثانية، PCM16 24 kHz
seen = {}                                         # قيم nonce المستخدمة


def signed_call(header, target):
    """يتحقق من X-Pabal-Voice-Signature ويعيد call id الموقّع. وإن كان خاطئاً فـ None."""
    try:
        parts = dict(p.split("=", 1) for p in header.split(","))
        t, nonce, call, v1 = int(parts["t"]), parts["nonce"], parts["call"], parts["v1"]
    except (KeyError, ValueError):
        return None
    now = time.time()
    if abs(now - t) > 300 or nonce in seen:
        return None
    text = f"pabal-voice-stream/1\n{t}\n{nonce}\n{call}\n{PUBLIC_HOST}\n{target}"
    expected = hmac.new(SECRET, text.encode("utf-8", "surrogateescape"), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected.encode(), v1.encode("utf-8", "surrogateescape")):  # بايتاً ببايت
        return None
    for old in [n for n, at in seen.items() if at < now - 600]:
        del seen[old]
    seen[nonce] = now
    return call


def check(connection, request):
    if request.path.split("?", 1)[0] != PATH:
        return connection.respond(404, "not here\n")
    call = signed_call(request.headers.get("X-Pabal-Voice-Signature", ""), request.path)
    if call is None:
        return connection.respond(401, "bad signature\n")
    connection.signed_call = call                 # يجب أن يتحدث start عن هذه المكالمة
    return None


async def handle(ws):
    if ws.subprotocol != "pabal.voice.v1":
        await ws.close(1002, "pabal.voice.v1 only")
        return
    start = json.loads(await asyncio.wait_for(ws.recv(), 5))
    if (start.get("event") != "start" or start.get("call_id") != ws.signed_call
            or (BOT_ID and start.get("bot_id") != BOT_ID)):
        await ws.close(1008, "not this call")
        return
    await ws.send(json.dumps({"event": "ready"}))    # خلال 3 ثوانٍ، وقبل أي شيء
    live = False
    async for message in ws:
        if isinstance(message, bytes):
            if live:
                await ws.send(message)            # يعيد قطعة المتصل ذات الـ 20 ms كما هي (صدى)
            continue
        event = json.loads(message)
        if event.get("event") == "live" and not start.get("probe"):   # انتهى إعلان الذكاء الاصطناعي
            live = True
            await ws.send(CHIME)
            await ws.send(json.dumps({"event": "mark", "name": "chime"}))
        elif event.get("event") == "stop":
            print("انتهت:", start["call_id"], event.get("reason"), flush=True)
            break


async def main():
    tls = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    tls.minimum_version = ssl.TLSVersion.TLSv1_2
    tls.load_cert_chain(os.environ["TLS_CERT"], os.environ["TLS_KEY"])
    async with serve(handle, "0.0.0.0", PORT, ssl=tls, subprotocols=["pabal.voice.v1"],
                     process_request=check, max_size=65536, compression=None):
        print(f"يستمع على: wss://{PUBLIC_HOST}:{PORT}{PATH}", flush=True)
        await asyncio.get_running_loop().create_future()


asyncio.run(main())

ما يراه المتصل

  • زر الاتصال: يظهر زر الاتصال أعلى المحادثة وفي الملف الشخصي للبوت الصوتي. ولا يظهر إلا إذا قرر الخادم أن مكالمة ذلك الشخص · على ذلك الجهاز يمكن استقبالها (وقبل الإطلاق العام للمُختبِرين فقط). ولا توجد مكالمات فيديو.
  • نافذة التأكيد (قبل إذن الميكروفون): العنوان «An AI answers this call» (يرد الذكاء الاصطناعي على هذه المكالمة)، وأن البوت ليس إنساناً والصوت من صنع الذكاء الاصطناعي، وأن المكالمة ليست مشفّرة من طرف إلى طرف، وجهة استلام الصوت (Google، وتحديداً Gemini Live API في Vertex AI) والبلد · المنطقة (United States (Iowa, Google Cloud us-central1))، وما يُرسل (صوتك أثناء المكالمة، ونص المحادثة وردود الذكاء الاصطناعي، والملاحظات التي يضيفها البوت أثناء المكالمة ونتائج وظائفه)، والاحتفاظ (لا يخزّن Pabal الصوت ويحتفظ بسجل المكالمات 180 يوماً فقط، ولا تحتفظ Google بشيء بعد توليد الرد، لكن المحتوى الذي تشتبه الفحوص الآلية في أنه إساءة استخدام قد يُسجَّل حتى 90 يوماً)، وما يستلمه مالك البوت (الاسم · رقم الحساب · اللغة، ووقت المكالمة · مدتها، والقيم المستخدمة في الأدوات — لا رقم الهاتف ولا الصوت)، والتاسعة عشرة فأكثر. والزران هما «I'm 19 or older · Call» وإلغاء. والعمر ليس تحققاً من الهوية بل تأكيد ذاتي. وزر الاتصال هو الزر الرئيسي (بلون التمييز المملوء)، والإلغاء زر ثانوي. وعلى الخادم الذي فُعّل فيه الاحتفاظ بالمحادثة تُضاف بعد الشرح خانة الاختيار «Keep this call's conversation as text in my chat» (احتفظ بمحادثة هذه المكالمة نصاً في محادثتي؛ معطّلة افتراضياً).
  • كم مرة يُسأل: في سطح المكتب · Android عند أول اتصال بذلك البوت (ومجدداً إذا تغيّرت جهة الاستلام أو نسخة النافذة)، وفي iPhone وApple Watch في كل مرة. وتعرض الساعة النافذة كاملةً في كل مرة، والقيمة الأولى لمفتاح الاحتفاظ بالمحادثة هي اختيار آخر مكالمة لذلك البوت. ويرسل التطبيق الموافقة إلى الخادم مع كل مكالمة، ويستخدم الخادم موافقة واحدة من الجهاز نفسه · للبوت نفسه · خلال دقيقتين لتلك المكالمة مرة واحدة فقط.
  • عند الاتصال: إعلان الذكاء الاصطناعي «You're now talking to an AI voice bot.» (وفي بوت الصوت الخام «This is an AI voice bot. The call's audio goes to the bot developer's server.») ← (في بوت نص المحادثة) إعلان نص المحادثة ← تحية البوت. ولا يستطيع مالك البوت إيقاف هذه الإعلانات، وكلام المتصل قبل انتهائها لا يذهب إلى النموذج. ولا إعلان صوتياً للاحتفاظ بالمحادثة — لأن المتصل اختاره بنفسه على الشاشة.
  • لغة الإعلانات: لا تأتي الإعلانات الصوتية بلغة البوت (language)، بل بلغة نافذة التأكيد التي قرأها المتصل. وإعلانات المكالمة الواحدة كلها بلغة واحدة — إعلان الذكاء الاصطناعي، وإعلان نص المحادثة، والاعتذار عند انقطاع الاتصال (وفي بوت الصوت الخام: إعلان الصوت الخام والاعتذار). فإذا لم يكن لأيٍّ منها بتلك اللغة تسجيل اكتملت مراجعته جاءت كلها بالإنجليزية. واللغتان الوحيدتان اللتان اكتملت مراجعتهما حالياً هما الكورية والإنجليزية. أما نافذة التأكيد والإعلانات باللغات الثماني الأخرى (اليابانية والصينية والفرنسية والإسبانية والإندونيسية والفيتنامية والخميرية والعربية) فمسودات تنتظر المراجعة القانونية وتدقيق متحدث أصلي، ولذلك فإن المتصل الذي يستخدم التطبيق بإحدى هذه اللغات يرى حالياً نافذة التأكيد بالإنجليزية ويسمع الإعلانات بالإنجليزية. ولا تزال تسجيلات الخميرية والنسخة اليابانية من «Just a moment longer, please.» تنتظر أن يستمع إليها شخص ويختار الأنسب منها. ويسبق أولَ إعلان في المكالمة وإعلانَ نص المحادثة دائماً صمتٌ لا يقل عن 300 ms — كي لا يبتلع مسار الهاتف الكلمة الأولى.
  • شاشة المكالمة: تحت الاسم «@{username} · AI · Bot» و«AI-generated voice · Call audio is processed by Google». ولا يوجد مفتاح الرموز التعبيرية ولا عبارة التشفير.
  • إذا حدثت مشكلة في الاتصال يسمع المتصل «Sorry, we've lost the connection. Ending the call now.» ثم تنقطع المكالمة. وكل أنواع الرفض تنتهي بإشارة انشغال.

والشرح الموجّه لمستخدمي التطبيق في استخدام التطبيق — الاتصال ببوت صوتي.

المكالمات من البوتات

معطّلة على pabal.me — بانتظار المراجعة القانونية

الخادم يدعمها، لكن لا يستطيع أي بوت الاتصال بأحد قبل أن يجتاز قسم bot-calls-outbound في سياسة الخصوصية وقسم voice-bot-owners-outbound في شروط الاستخدام المراجعةَ القانونية ويُنشرا. ولم يصدر بعدُ أي إصدار من التطبيق فيه نافذة الإذن (الحالة الحالية). وما يلي يصف طريقة العمل على خادم فُعّلت فيه هذه الميزة.

يمكن للبوت الصوتي أيضاً أن يتصل بشخص أولاً: لتأكيد موعد، أو للإيقاظ في وقت اختاره، أو للإبلاغ عن توصيل. يطلب كود مالك البوت ذلك عبر startVoiceCall، فيفحص الخادم الطلب ويُرنّ أجهزة الشخص، وإذا ردّ يُشغَّل إعلان Pabal أولاً ثم يتكلم نموذج الصوت بالذكاء الاصطناعي وفق تعليمات البوت. وتصل طريقة انتهاء المكالمة في تحديث voice_call.

  • فقط لمن أذِن. يجب أن يأذن الشخص لذلك البوت بالاتصال به في تطبيق Pabal (إذن المتلقّي)، ويمكنه إيقاف الإذن في أي وقت.
  • ليست مكالمات إعلانية. هناك أربعة أغراض — التذكير والإيقاظ والتوصيل والمواعيد — ولا يُسمح بالإعلان ولا بعروض البيع ولا بالترويج (شروط مالكي البوتات، البنود 12–24).
  • المكالمات من البوتات ليست مشفّرة من طرف إلى طرف — يمر الصوت عبر خادم Pabal ومزوّد الذكاء الاصطناعي. عندما يردّ الشخص يُرسَل الصوت إلى Google أثناء المكالمة — واجهة Gemini Live البرمجية في Vertex AI، في آيوا بالولايات المتحدة (Google Cloud us-central1). ولا يحفظ Pabal الصوت.
  • لا يصل نص المحادثة إلى مالك البوت. المكالمات التي يجريها البوت ليس لها نص محادثة. وما يتلقاه مالك البوت هو الأمور الخمسة المذكورة في ما يراه المتلقّي وما يسمعه.

سير العمل

كود البوت ⁧خادم Pabal⁩ البوابة · النموذج تطبيق الشخص ① startVoiceCall الفحص: البوت · الإذن · الحدود ساعات الهدوء · التوقف المؤقت ② 200 {voice_call_id} ③ dial ⁧④ الرنين: الاسم (AI)⁩ ⑤ الرد ⁧⑥ إعلان Pabal⁩ ⁧⑦ البوت: الاسم · AI · الغرض⁩ ⁧المكالمة — يمر الصوت عبر البوابة ونموذج AI⁩ ⑧ voice_call: started · ended ⁧لا رد · لا رنين: not_answered⁩
مكالمة واحدة من بوت: الطلب ← الفحص ← الرنين ← الرد ← الإعلان ← الكلمات الأولى ← النهاية

شرح المخطط

  • أربعة مسارات: من اليسار، كود مالك البوت، وخادم Pabal، وبوابة الصوت مع نموذج الذكاء الاصطناعي، وتطبيق المتلقّي. الخطوط المنقّطة العمودية هي الزمن؛ والخطوط الزرقاء المتصلة هي المكالمة وهي تتقدم، والخط الرمادي هو الرد على البوت.
  • الإطار الرمادي عند ① — ثلاثة فحوص: قبل الرد، جانب البوت (هل فُعّلت الميزة، ومراجعة المشغّل، والشروط، والحدود) ووجود الإذن؛ وبعد 200، جانب الشخص (ساعات الهدوء، والتوقف المؤقت، والحد اليومي والفاصل الزمني للشخص، وجهاز يمكن أن يرنّ)؛ ومرة أخرى قبل الرنين مباشرة.
  • الرمز 200 عند ② يعني «استُلم» فقط. أما هل رنّ الهاتف وهل ردّ الشخص فلا تخبر بهما إلا التحديثات عند ⑧.
  • ④–⑦ جانب الشخص: يظهر في الرنين «(AI)» بعد اسم البوت، وعند الرد يُشغَّل إعلان Pabal (⑥) قبل البوت. وفي كلماته الأولى (⑦) يذكر البوت اسمه وأنه ذكاء اصطناعي والغرض من المكالمة. وطوال الإطار الأخضر يمر الصوت عبر البوابة ونموذج الذكاء الاصطناعي — فهو ليس مشفّراً من طرف إلى طرف.
  • الخط الأحمر المنقّط هو الأهم: الطلب الذي لم يرنّ قط بسبب جانب الشخص، والطلب الذي رنّ دون أن يردّ أحد، ينتهيان بالطريقة نفسها وفي الوقت نفسه بـnot_answered. فلا يستطيع البوت أن يعرف هل كان الشخص نائماً أم في مكالمة أخرى.

التفعيل — @BotFather

لا يفعّل المكالماتِ من البوت إلا مالكُه عبر @BotFather، ولا يمكن ذلك بالرمز المميّز أبداً (إرسال حقول الاتصال إلى setVoiceCallSettings يعطي 400 VOICE_FIELD_NOT_AVAILABLE). يجب أن تكون المكالمات الصوتية مفعّلة أولاً. /mybots ← البوت ← المكالمات الصوتية (음성 통화) ← المكالمات من البوت (봇이 거는 전화) ← تفعيل (켜기).

켜기
BotFather@order_helper_bot 이 사람에게 전화를 걸 수 있게 할까요? • 이 봇에게 전화를 허락한 사람에게만 걸어요. 허락은 그 사람이 파발 앱에서 하고, 언제든 끌 수 있어요. • 그 사람의 조용한 시간과 쉬는 중에는 울리지 않아요. • 하루 상한(지금 50번)과 서버의 한도가 있어요. • 운영자가 이 봇의 이름·사진·지시문·목소리·목적을 검토하고 허락해야 걸려요. 무엇이든 바뀌면 다시 검토해요. • 봇이 거는 전화는 종단 간 암호화가 아니에요. 통화 소리는 파발 서버와 AI 제공자를 거쳐요.
켜기
BotFather봇이 거는 전화를 켰어요. 운영자가 이 봇을 검토하고 허락하면 Bot API 의 startVoiceCall 로 걸 수 있어요. 봇이 거는 전화 · @order_helper_bot 상태: 켜짐 운영자 허락: 대기 목적: 배달 하루 상한: 50번 오늘: 요청 0 · 받음 0 · 안 받음 0 이 봇에게 전화를 허락한 사람에게만, 그 사람의 조용한 시간과 쉬는 중을 피해 걸어요. 봇이 거는 전화는 종단 간 암호화가 아니에요 — 통화 소리는 파발 서버와 AI 제공자를 거쳐요.

يجيب BotFather بالكورية. الرسالة الأولى تسأل هل يُسمح للبوت بالاتصال بالناس: فقط بمن أذِن، ولا يرنّ أبداً في ساعات هدوئهم أو أثناء التوقف المؤقت، ضمن حد يومي (50 الآن) وحدود الخادم، وبعد أن يراجع المشغّل اسم البوت وصورته وتعليماته وصوته وأغراضه ويأذن بها (ويعيد المراجعة كلما تغيّر شيء منها)؛ والمكالمات من البوتات ليست مشفّرة من طرف إلى طرف. والرسالة الثانية تعرض شاشة البوت بعد التفعيل: مفعّل، بانتظار المشغّل، الغرض التوصيل، الحد 50، وطلبات اليوم.

  • الشروط، النسخة 4: إن لم توافق عليها بعد، تظهر شروط مالكي البوتات قبل التفعيل. والبنود 12–24 هي التزامات المكالمات من البوتات.
  • الأغراض: لا تُفعَّل الميزة إلا باختيار غرض واحد على الأقل من reminder (التذكير) · wake_up (الإيقاظ) · delivery (التوصيل) · appointment (المواعيد). ويذكر كل طلب واحداً منها؛ والغرض الذي لم يختره البوت يعطي 400. وتغيير الأغراض يعيد طلب الإذن ممن أذِنوا، ويعيد المشغّل المراجعة.
  • الحد اليومي: عدد الطلبات التي يمكن للبوت إرسالها يومياً — 10 أو 20 أو 50 (الافتراضي 50).
  • مراجعة المشغّل: يطّلع المشغّل في صفحة الإدارة على اسم البوت و@اسم المستخدم والصورة والتعليمات والتحية والأدوات والصوت واللغة والوضع والأغراض واسم المالك، ثم يأذن. وإذا تغيّر أي منها لا يستطيع البوت الاتصال حتى يُؤذن له من جديد (403 VOICE_OUTBOUND_OFF: review).
  • الرمز المميّز: تغيير إعدادات الصوت بالرمز المميّز (عدا إيقاف الصوت) يجمّد المكالمات من البوت 24 ساعة (frozen). إن كنت أنت من غيّرها فارفع التجميد بزر أنا من غيّرها (내가 바꿨어요، vdk·vdK) في @BotFather. وإعادة إصدار الرمز المميّز تجمّد أيضاً 24 ساعة، وتنهي المكالمات الجارية، وتحذف الـwebhook لبوت فُعّلت مكالماته.
  • لا يتصل إلا البوت الذي لغته الكورية أو الإنجليزية (ko-KR·en-US). والبوت الذي يعمل بوضع الصوت الخام أو بمفتاح المالك أو بأدوات أو بخوادم MCP لا يستطيع ذلك (mode).

إذن المتلقّي

لا يستطيع البوت الحصول على الإذن بنفسه. يفتح الشخص نافذة الإذن لذلك البوت في تطبيق Pabal ويضغط «I am 19 or older · Allow» (عمري 19 عاماً أو أكثر · السماح). وتذكر النافذة أن الذكاء الاصطناعي هو من يتصل، والأغراض، واسم مالك البوت، ووجهة الصوت، وساعات الهدوء (استخدام التطبيق — السماح لبوت بالاتصال بك). وتُفتح النافذة من ثلاثة أماكن.

  • قائمة البوت: «Allow calls…» (السماح بالمكالمات…) في محادثة البوت أو ملفه الشخصي.
  • رابط في المحادثة الفردية: يرسل البوت https://pabal.me/<bot username>?callme=1 إلى محادثته الفردية مع الشخص، رابطاً في النص أو زر URL مضمّناً. ولا تُفتح النافذة إلا لرسالة أرسلها ذلك البوت في تلك المحادثة — أما الرابط المُعاد توجيهه إلى محادثة أخرى أو المعاينة أو الويب فيفتح المحادثة فقط. ويمكن للشخص فتح النافذة من رابط مرة واحدة في اليوم (لكل البوتات معاً)، ولا يمكنه ذلك أبداً إن لم يكتب للبوت قط.
  • القائمة في الإعدادات «Bots allowed to call me» (البوتات المسموح لها بالاتصال بي): عرض البوتات المأذون لها وإيقافها والإذن لها من جديد.
curl -s "https://pabal.me/bot$BOT_TOKEN/sendMessage" -d chat_id=$USER_ID \
  --data-urlencode "text=هل تريد أن نتصل بك قبل وصول طلبك؟" \
  --data-urlencode 'reply_markup={"inline_keyboard": [[{"text": "السماح بالمكالمات", "url": "https://pabal.me/order_helper_bot?callme=1"}]]}'
  • الخوادم المستضافة ذاتياً: لا تعرف التطبيقات إلا روابط pabal.me. ولخادم آخر يجب إضافة مضيفه إلى قواعد الروابط في إصدار التطبيق حتى يفتح الرابط النافذة (أما القائمة وقائمة الإعدادات فتعملان كما هما).
  • في الوقت الحالي: لا يستطيع تفعيل الإذن إلا الحسابات ذات الرقم +82، على جهاز منطقته الزمنية كوريا (سيول) ومضى على تسجيل الدخول فيه 24 ساعة على الأقل (والمشغّل يحدد البلدان). ومن يجيب بأن عمره أقل من 19 عاماً لا يستطيع إجراء مكالمات مع البوتات مدة سنة.
  • إذا تغيّر اسم البوت أو مالكه أو أغراضه، أو مضت سنة على الإذن، يُسأل الشخص من جديد؛ وحتى ذلك الحين لا يستطيع البوت الاتصال.

الاتصال — startVoiceCall

curl -s "https://pabal.me/bot$BOT_TOKEN/startVoiceCall" -d user_id=$USER_ID -d purpose=delivery \
  --data-urlencode "context=يصل الطلب A-1042 اليوم نحو الساعة 3 عصراً. اسأله هل يستطيع استلامه حينها." \
  -d max_duration=180 -d ring_timeout=30
{"ok": true, "result": {"voice_call_id": "8812345678901234"}}
المعاملالمعنى
user_idمعرّف الشخص المراد الاتصال به، ويجب أن يكون قد أذِن لهذا البوت
purposeأحد الأغراض التي اختارها هذا البوت: reminder · wake_up · delivery · appointment
contextما يُقال للنموذج وحده عن هذه المكالمة: من 1 إلى 1,000 حرف، و3,072 بايت. ويُفحص بقواعد المحتوى (انتحال الشخصية، وإخفاء أنه ذكاء اصطناعي، وطلب الأسرار، والإعلان). ويستخدمه النموذج بعد كلماته الأولى (الاسم، والذكاء الاصطناعي، والغرض)، بعد أن يردّ الشخص
max_durationأقصى مدة للمكالمة بعد الرد (بالثواني): من 30 إلى الأصغر بين 300 والحد الأقصى للبوت. الافتراضي 180
ring_timeoutمدة الرنين (بالثواني)، 30–45. الافتراضي 40
الردالمعنىما يجب فعله
200 {"voice_call_id": "…"}استُلم. أما هل رنّ وهل ردّ أحد فيصل في التحديثات—
400 Bad Request: <what>معامل خاطئ، أو غرض لم يختره البوت (purpose is not declared)، أو لغة بوت غير الكورية والإنجليزيةصحّحه
400 VOICE_CONTEXT_REFUSED: <code>context يخالف قواعد المحتوىصحّحه
403 VOICE_OUTBOUND_OFF: <reason>جانب البوت: owner_off (غير مفعّل) · terms (الشروط، النسخة 4) · review (بانتظار مراجعة المشغّل) · suspended (إيقاف تلقائي) · frozen (تجميد بسبب الرمز المميّز) · owner_name (اسم المالك لم يجتز فحص انتحال الشخصية) · mode، أو سبب من جانب البوت في إعدادات الصوت (unavailable_reason): bot_voice_off · not_allowed · suspended · name_refused · text_refused · awaiting_review · stream_unavailable · own_key_unavailable · no_gateway · no_gateway_with_tools · no_gateway_with_language · no_own_key_gateway. أما أسباب جانب الخادم voice_off · kill_switch · no_relay · outbound_off فتأتي بالرمز 503 لا 403@BotFather · المشغّل
403 USER_UNAVAILABLEلا يمكن الاتصال بهذا الشخص: لا إذن، أو أُوقف أو يحتاج إلى إذن جديد، أو حُظر البوت، أو ليس شخصاً، أو خارج النطاق، أو دون 19 عاماً، أو ليس مُختبِراً. والمحتوى هو نفسه دائماً مهما كان السبباطلب الإذن
429 retry after Nحد من جانب البوت أو المالك: معدل الطلبات، والطلبات اليومية للبوت أو المالك، والمكالمات المتزامنة للبوت، ودقائق الاتصال الصادر للمالك، ووقت المكالمات اليومي للبوت أو المالك (مشترك مع المكالمات الواردة إلى البوت)بعد N ثانية
503 VOICE_UNAVAILABLEجانب الخادم: مفتاح المكالمات الصادرة مطفأ، أو الصوت مطفأ على الخادم (بما في ذلك حين تخلو سياسة الخصوصية من قسم مكالمات البوتات)، أو مفتاح الإيقاف الطارئ، أو لا خادم ترحيل للمكالمات، أو حدود الخادم وحصة المكالمات الصادرة، أو لا بوابة تستطيع الاتصال (أو كلها ممتلئة)، أو تعذّر تسجيل الطلبلاحقاً
{"ok": false, "error_code": 403, "description": "Forbidden: USER_UNAVAILABLE"}

يُفحص جانب الشخص بعد 200. حتى مع وجود الإذن، إذا كان الوقت ضمن ساعات هدوء الشخص، أو أثناء التوقف المؤقت، أو تجاوز الحد اليومي مع ذلك الشخص أو حدّه اليومي وفاصله الزمني، أو لم يكن هناك جهاز يرنّ، أو كان في مكالمة أخرى، فلا يرنّ شيء ويصل not_answered بعد نحو ring_timeout + 12 ثانية من الطلب — تماماً كرنين لم يردّ عليه أحد. والإذن لا يغيّر جانب البوت (403 · 429 · 503).

الإعدادات والاستخدام

"outbound": {
  "enabled": true,
  "purposes": ["delivery"],
  "daily_cap": 50,
  "reviewed": true,
  "frozen_until": 0,
  "suspended": false,
  "available": true
}
"outbound": {
  "today": {"requests": 4, "answered": 2, "not_answered": 2, "minutes": 3},
  "limits": {"daily": 50, "owner_daily": 100, "owner_minutes": 30}
}
  • outbound في getVoiceCallSettings موجود دائماً: ما ضُبط في @BotFather (enabled·purposes·daily_cap)، ومراجعة المشغّل (reviewed)، ووقت انتهاء التجميد بسبب الرمز المميّز (frozen_until، و0 إن لم يكن مجمّداً)، والإيقاف التلقائي (suspended)، وهل يستطيع البوت الاتصال الآن (available). ولا يظهر unavailable_reason إلا عندما تكون available بقيمة false (من جانب البوت بالقيم نفسها لأسباب 403 VOICE_OUTBOUND_OFF، ومن جانب الخادم voice_off · kill_switch · no_relay · outbound_off — وهذه الأربع تقابل 503 VOICE_UNAVAILABLE).
  • outbound في getVoiceCallUsage لا يظهر إلا لبوت فعّل الاتصال ولو مرة: طلبات اليوم والمردود عليها وغير المردود عليها والدقائق، والحدود اليومية المطبّقة على هذا البوت. ولا يُعطى عدد مرات الرنين. والحقول calls·seconds·missed·refused لا تحسب أبداً المكالمات التي أجراها البوت. لكن المكالمات التي يجريها البوت تستهلك وقت المكالمات اليومي للبوت ومالكه نفسه الذي تستهلكه المكالمات الواردة إلى البوت: تُحتسب ثواني المكالمة التي رُدّ عليها، فتنقص bot وowner في remaining_seconds_today — وأثناء الرنين تنقص أيضاً بقدر ما تحجزه المكالمة، ويعود ذلك إن لم يرد أحد.
  • لا يمكن تغييرها عبر setVoiceCallSettings (400 VOICE_FIELD_NOT_AVAILABLE).

التحديثات

تصل كتحديثات voice_call نفسها التي تصل للمكالمات إلى البوت (اكتب voice_call في allowed_updates). وقيمة direction هي "outgoing"، ولا يظهر الشخص إلا بالمعرّف في to (بلا اسم ولا اسم مستخدم ولا رقم هاتف).

{
  "update_id": 83,
  "voice_call": {
    "id": "8812345678901234",
    "direction": "outgoing",
    "event": "not_answered",
    "to": {"id": 100005},
    "purpose": "delivery",
    "date": 1790000123
  }
}
{
  "update_id": 81,
  "voice_call": {
    "id": "8812345678901234",
    "direction": "outgoing",
    "event": "started",
    "to": {"id": 100005},
    "purpose": "delivery",
    "date": 1790000130,
    "max_duration": 180
  }
}
{
  "update_id": 82,
  "voice_call": {
    "id": "8812345678901234",
    "direction": "outgoing",
    "event": "ended",
    "to": {"id": 100005},
    "purpose": "delivery",
    "date": 1790000250,
    "start_date": 1790000130,
    "duration": 120,
    "max_duration": 180,
    "end_reason": "callee"
  }
}
{
  "update_id": 84,
  "voice_call": {
    "id": "8812345678901234",
    "direction": "outgoing",
    "event": "failed",
    "to": {"id": 100005},
    "purpose": "delivery",
    "date": 1790000110
  }
}
eventمتىحقول إضافية
startedردّ الشخص واتصلت المكالمة. وعلى Apple Watch عندما ينضم صوت الساعة (وإن لم ينضم أبداً فـended مدته 0 ثانية مع failed)max_duration
endedانتهت مكالمة تم الرد عليها. ويصل متأخراً حتى 10 ثوانٍ أياً كانت إعدادات الخادم، لأن الخادم ينتظر استخدام البوابةstart_date، duration، max_duration، end_reason
not_answeredطلب لم يردّ عليه أحد — دون تمييز بين رنين بلا رد، أو رفض، أو عدم رنين أصلاً بسبب جانب الشخص. بعد نحو ring_timeout + 12 ثانية من الطلبلا شيء
failedانقطع الاتصال بالبوابة التي تتولى الطلب قبل الردلا شيء

end_reason: callee (أنهى المتلقّي المكالمة) · bot (أنهاها البوت، أو أُعيد إصدار رمزه المميّز) · max_length (أقصى مدة) · failed (فشل الاتصال بعد الرد) · operator (المشغّل أو مفتاح الإيقاف الطارئ) · suspended (إيقاف البوت أو المالك). والمكالمات التي يجريها البوت ليس فيها missed ولا refused ولا ringing — فسرعة الرد قد تكشف هل كان الشخص قرب هاتفه — ولا transcript (معطّل بحكم التصميم).

أثناء المكالمة

  • endVoiceCall: قبل إبلاغ البوت بالنتيجة، يسحب الطلب سواء كان يرنّ أم لم يرنّ بعد (true، ثم not_answered). وينهي المكالمة التي تم الرد عليها (end_reason bot). وسحب الطلب خلال 15 ثانية منه يوقف الثنائي مؤقتاً ساعة واحدة.
  • sendVoiceCallContext: بعد أن يردّ الشخص فقط (وقبل ذلك VOICE_CALL_NOT_FOUND). ثلاث مرات لكل مكالمة، و1,000 حرف و3,072 بايت، وبقواعد المحتوى نفسها التي لـcontext. وإن كانت البوابة لا تقبل الملاحظات فـVOICE_CONTEXT_NOT_SUPPORTED.

ما يراه المتلقّي وما يسمعه

  • الرنين: «(AI)» بعد اسم البوت، و«AI voice bot · Pabal never asks for codes» (بوت صوتي بالذكاء الاصطناعي · لا يطلب Pabal رموزاً أبداً).
  • عند الرد يُشغَّل إعلان قبل البوت: «This is a Pabal AI voice call. We never ask for passwords, codes or money transfers. To stop these calls, turn off call permission in the bot's chat.» (هذه مكالمة صوتية بالذكاء الاصطناعي من Pabal. لا نطلب أبداً كلمات مرور أو رموزاً أو تحويلات مالية. لإيقاف هذه المكالمات، أوقف إذن الاتصال في محادثة البوت.) — بلغة نافذة الإذن التي قرأها الشخص (الكورية أو الإنجليزية)، ومن تطبيق بلغة أخرى بالإنجليزية. ولا يستطيع مالك البوت إيقافه.
  • كلمات البوت الأولى: اسم البوت، وأنه ذكاء اصطناعي، والغرض من المكالمة. ويأتي context بعد ذلك.
  • طرق الإيقاف: «Stop calls from this bot» (إيقاف المكالمات من هذا البوت) في القائمة، وحظر البوت، والإبلاغ عنه توقف الإذن دائماً. وقول شيء واضح أثناء المكالمة مثل «لا تتصل بي مرة أخرى أبداً» يوقفه فوراً (أما «توقّف» وحدها فمتروكة لتقدير النموذج).
  • التوقف المؤقت: بعد الرفض، أو رنين بلا رد، أو إنهاء المكالمة خلال 10 ثوانٍ من الرد، لا يستطيع البوت الاتصال بذلك الشخص مدة 24 ساعة.
  • سجل المكالمة الفائتة: إذا رنّ جهاز فعلاً، ولم يحظر الشخص البوت، ولم تكن في محادثة البوت رسالة غير مقروءة، يبقى «Missed AI bot call» (مكالمة فائتة من بوت بالذكاء الاصطناعي — مقروءة، بلا إشعار). وإن كانت هناك رسالة غير مقروءة، أو لم يرنّ بسبب ساعات الهدوء أو التوقف المؤقت، فلا سجل. وكل طلب من البوت يُحفظ دون استثناء في أدلة البلاغات (30 يوماً).
  • ما يتلقاه مالك البوت (بترتيب نافذة الإذن):
    1. معرّف الشخص في Pabal
    2. هل الإذن مفعّل (تكشفه الردود على طلبات الاتصال)
    3. هل ردّ الشخص
    4. وقت بدء المكالمة ومدتها
    5. من أنهى المكالمة
    المكالمة الفائتة والمكالمة المرفوضة تبدوان متطابقتين. ولا يُرسَل ما قيل في المكالمة.
  • الأجهزة المشتركة: يمكن لأي جهاز مسجّل الدخول أن يردّ، ويذكر البوت الغرض وما أُخبر به لمن يردّ أياً كان. وعلى حاسوب مشترك ذكّر الناس بتسجيل الخروج.
  • شاشة القفل: على Android 14 وما بعده، إن لم يسمح الشخص بإشعارات ملء الشاشة، تصل المكالمة كإشعار بدلاً من شاشة مكالمة فوق شاشة القفل. أما iPhone وApple Watch فيرنّان كمكالمة هاتفية.
  • الاحتفاظ بالمحادثة يحفظها في محادثة الشخص نفسه فقط، إن اختاره في نافذة الإذن. ومع تفعيل الرد على الساعة في تطبيق Pabal لـApple Watch، ترنّ Apple Watch أيضاً.
  • المكالمات من البوتات ليست مشفّرة من طرف إلى طرف — يمر الصوت عبر خادم Pabal ومزوّد الذكاء الاصطناعي.

الحدود · ساعات الهدوء · التوقف المؤقت · الإيقاف التلقائي

الحدالافتراضيعلى خادم الإنتاج
بوت واحد إلى شخص واحد (يومياً)3 مكالماتيمكن للمشغّل خفضه فقط
إلى شخص واحد (يومياً، كل البوتات)6 مكالماتيمكن خفضه فقط
الفاصل بين مكالمتين إلى شخص واحد10 دقائقيمكن رفعه فقط
طلبات بوت واحد (يومياً)50 مكالمةيختار المالك 10 أو 20 أو 50 في @BotFather
مالك بوتات واحد (يومياً، كل بوتاته)100 مكالمة · 30 دقيقةعدد المكالمات يمكن خفضه فقط
حتى يستطيع البوت إرسال طلبه التالي10 ثوانٍيمكن رفعه فقط
دقائق الاتصال الصادر للخادم30% من دقائق الخادم اليوميةيمكن خفضها فقط
ساعات الهدوء21:00–08:00يمكن للمشغّل وللشخص توسيعها فقط. وفق المنطقة الزمنية للجهاز
نافذة الإيقاظ (wake_up فقط)120 دقيقة كحد أقصىيختارها الشخص ضمن ساعات هدوئه
الرنين (ring_timeout)30–45 ثانية40 ثانية إن لم يحدده الطلب
التوقف المؤقت — الرفض، أو رنين بلا رد، أو إنهاء المكالمة خلال 10 ثوانٍ من الرد24 ساعةيمكن رفعه فقط
التوقف المؤقت — سحب البوت طلبه خلال 15 ثانيةساعة واحدة (1)يمكن رفعه فقط
  • «يومياً» يعني يوماً وفق المنطقة الزمنية للخادم (TELEGRAM_VOICE_TIME_ZONE). ولا تزيد المكالمة أبداً على 300 ثانية.
  • المكالمات من البوتات تستهلك أيضاً وقت المكالمات اليومي نفسه الذي تستهلكه المكالمات الواردة إلى البوت (60 دقيقة للبوت · 120 دقيقة للمالك). وعند نفاده يتلقى startVoiceCall الرمز 429، وتُرفض المكالمات الواردة إلى البوت أيضاً (refused، budget).
  • ينتهي التوقف المؤقت عند انقضاء الوقت، أو عندما يتصل الشخص بالبوت، أو يضغط «I can take calls now» (يمكنني تلقي المكالمات الآن)، أو يأذن من جديد. ولا تنهيه رسالة البوت ولا أزراره.
  • الإيقاف التلقائي يوقف مكالمات البوت إلى الناس فقط (403 VOICE_OUTBOUND_OFF: suspended) ويستمر حتى يتحقق المشغّل ويرفعه: معدل رد أقل من 20% في آخر 20 مكالمة، أو 3 بلاغات خلال 7 أيام من أشخاص رنّ لهم، أو إيقاف 30% أو أكثر من أشخاصه للإذن (مع 10 أشخاص على الأقل)، أو مكالمتان أوقفتهما القواعد أثناء المكالمة، أو 5 سحوبات خلال 15 ثانية، أو ردّ شخصين أو أقل مع 60 دقيقة أو أكثر من المكالمات المردود عليها.

الحدود

الحدالافتراضيمن يحدده
أقصى مدة للمكالمة الواحدةالبوت 300 ثانية (النطاق الممكن من 60 إلى سقف المشغّل · الخادم، الافتراضي 600)مالك البوت ضمن سقف المشغّل · الخادم. وأزرار @BotFather هي 1 · 3 · 5 · 10 دقائق
المكالمات المتزامنة للبوت2 (من 1 إلى سقف المشغّل · الخادم، الافتراضي 2)مالك البوت ضمن سقف المشغّل · الخادم
وقت المكالمات اليومي للبوت الواحد60 دقيقةالمشغّل
الحد اليومي لمالك البوت الواحد (كل البوتات معاً)120 دقيقةالمشغّل
الحد اليومي للخادم كله300 دقيقةالمشغّل
المتصل الواحد60 دقيقة في اليوم، و20 مكالمة في الساعةالمشغّل
أقصر مكالمة تُقبل60 ثانية — إذا كان الوقت المتبقي أقل من ذلك فلا تُقبلالمشغّل
الحسابات الجديدةمن لم يمضِ على تسجيله 7 أيام لا يستطيع الاتصال — ويُستثنى المُختبِرون، ومن سبق أن أرسل إليه البوت رسالة قبل يوم أو أكثرالمشغّل
تغيير الإعدادات30 مرة في الساعة لكل بوت—
الملاحظات أثناء المكالمة10 في الدقيقة لكل مكالمة—
الأدوات16 · 8KB، والاستعلامات المفتوحة: 2 لكل مكالمة · 8 لكل بوت، وفي الدقيقة: 30 لكل مكالمة · 120 لكل بوت—
اختبار مفتاح المالك · فحص الصوت الخام5 مرات في الساعة لكل بوت، و10 لكل مالك—
تسجيل MCP · فحصه10 مرات في الساعة لكل بوت، و20 لكل مالك، وخادمان لكل بوت—

لا يمكن من Apple Watch الاتصال بالبوتات التي تستخدم الأدوات · خوادم MCP · مفتاح المالك · وضع الصوت الخام · نص المحادثة لمالك البوت. ويصل هذا الرفض إلى المتصل وحده (403 WATCH_NOT_YET في تطبيق الساعة)، ولا يستلم البوت تحديث refused.

مدة المكالمة الواحدة هي الأصغر من بين ما تبقى اليوم للمتصل · البوت · المالك · الخادم وسقف المكالمة. وعند القبول تُحجز تلك المدة في الحدود الأربعة كلها، وعند الانتهاء لا يُحتسب إلا عدد الثواني الفعلي بساعة الخادم. ولا تستهلك مكالمات مفتاح المالك حدود البوت · المالك · الخادم (وتبقى حدود المتصل).

الأخطاء

الأخطاء بالشكل نفسه المستخدم في الطرق الأخرى: {"ok": false, "error_code": 400, "description": "Bad Request: VOICE_…"}. وأخطاء 403 بالشكل Forbidden: <code>: <description> (عدا USER_UNAVAILABLE الذي يكون دائماً Forbidden: USER_UNAVAILABLE تماماً بلا وصف)، وأخطاء 429 يُرفق بها parameters.retry_after.

الرمزالمعنى
403 VOICE_RULES_NOT_ACCEPTEDيجب الموافقة أولاً على قواعد البوتات الصوتية لدى @BotFather
403 VOICE_NOT_AVAILABLEعلى هذا الخادم لا يستطيع هذا البوت استخدام الصوت (الصوت معطّل · لم يسمح المشغّل) — لا يمكن تغيير الإعدادات، بل التعطيل والحذف فقط
403 VOICE_SUSPENDEDأوقف المشغّل صوت هذا البوت. ولا يمكن إلا التعطيل
403 VOICE_OWNER_TERMS_NOT_ACCEPTEDيجب الموافقة أولاً على شروط مالكي البوتات (النسخة 4)
403 VOICE_OWN_KEY_NOT_AVAILABLEهذا الخادم لا يقبل مفاتيح المالكين
403 VOICE_STREAM_NOT_AVAILABLEلا يمكن الآن استخدام وضع الصوت الخام على هذا الخادم
403 VOICE_OUTBOUND_OFF: <reason>لا يستطيع هذا البوت الاتصال بالناس الآن: owner_off · terms · review · suspended · frozen · owner_name · mode، أو سبب من جانب البوت في إعدادات الصوت (الاتصال). أما voice_off · kill_switch · no_relay · outbound_off فتأتي بالرمز 503
403 USER_UNAVAILABLEلا يمكن الاتصال بهذا الشخص — المحتوى نفسه دائماً مهما كان السبب، بما في ذلك غياب الإذن
503 VOICE_UNAVAILABLEلا يستطيع الخادم الاتصال بالناس الآن (مفتاح المكالمات الصادرة، الصوت مطفأ على الخادم، مفتاح الإيقاف الطارئ، لا ترحيل للمكالمات، حدود الخادم، البوابات، تعذّر تسجيل الطلب) — أعد المحاولة لاحقاً
Bad Request: purpose is not declared، Bad Request: the bot's voice language must be ko or en for outbound callsstartVoiceCall: غرض لم يختره البوت، أو لغة بوت غير الكورية والإنجليزية
VOICE_INSTRUCTIONS_REQUIRED، VOICE_INSTRUCTIONS_TOO_LONG، VOICE_GREETING_TOO_LONGالتعليمات مطلوبة · التعليمات تتجاوز 8,192 بايت · التحية تتجاوز 512 بايت
VOICE_INVALID، VOICE_LANGUAGE_INVALID، VOICE_LIMIT_INVALID، VOICE_MODE_INVALIDصوت أو لغة غير موجودين · حد خارج النطاق · وضع ليس managed/stream
VOICE_CONTENT_REFUSED: <field>: <code>، VOICE_CONTEXT_REFUSED: <code>قواعد المحتوى (الإعدادات، وcontext في startVoiceCall)
VOICE_TOOL_INVALID: <path>: <reason>تعريف الأداة (تعريف الأدوات)
VOICE_FIELD_NOT_AVAILABLE: …حقول غير مقبولة (model، api_key)، وحقول الصوت الخام في بوت يعمل بالوضع المُدار، أو حقول المكالمات من البوتات (outbound·purposes·daily_cap — في @BotFather فقط)
VOICE_CALL_NOT_FOUND، VOICE_CALL_NOT_ACTIVE، VOICE_CONTEXT_NOT_SUPPORTED، VOICE_CONTEXT_TOO_LONGطرق أثناء المكالمة (أثناء المكالمة)
VOICE_USAGE_PERIOD_INVALIDالفترة في getVoiceCallUsage
TOOL_QUERY_ID_INVALID، TOOL_QUERY_EXPIRED: …، TOOL_RESULT_…، TOOL_ERROR_TOO_LONGردود الأدوات (الرد)
API_KEY_INVALID، API_KEY_NO_REALTIME_ACCESS، 503 API_KEY_CHECK_UNAVAILABLE، VOICE_PROVIDER_INVALIDمفتاح المالك (مفتاح النموذج الخاص بالمالك)
STREAM_URL_INVALID، STREAM_SECRET_REQUIRED، STREAM_SECRET_INVALID، STREAM_PROBE_FAILED: …وضع الصوت الخام (الإعداد والفحص التجريبي)
MCP_…، MCP_OPT_IN_REQUIREDخوادم MCP (التسجيل). و503 VOICE_NOT_AVAILABLE: mcp: … أعد المحاولة بعد قليل، و409 MCP_CHECK_IN_PROGRESS أعد المحاولة بعد انتهاء الفحص
429 Too Many Requests: retry after Nحد الساعة (الإعدادات · اختبار المفتاح · الفحص التجريبي · MCP)، أو حد الملاحظات، أو حد من حدود المكالمات من البوتات (الحدود)

البوتات المتصلة عبر MTProto

  • البوت الذي يسجّل الدخول عبر MTProto فقط يستدعي طرق الصوت عبر bots.sendCustomRequest (باسم الطريقة وJSON نفسه). والمتاح: setVoiceCallSettings، getVoiceCallSettings، answerVoiceCallToolQuery، endVoiceCall، sendVoiceCallContext، getVoiceCallUsage، startVoiceCall، وطرق MCP الخمس. وما عداها METHOD_INVALID. ورد startVoiceCall هو {"voice_call_id": "…"}، ويصل الرفض كأخطاء RPC (USER_UNAVAILABLE، VOICE_OUTBOUND_OFF: review، FLOOD_WAIT_n، VOICE_UNAVAILABLE).
  • طرق لا يمكن استدعاؤها من رد الـwebhook: setVoiceCallApiKey، وstartVoiceCall، وطرق التسجيل والمفاتيح في MCP. إذا وُضعت في محتوى الرد على طلب POST من الـwebhook فلا تُنفَّذ — حتى لا تتحول رسالة أرسلها شخص مباشرةً إلى مكالمة هاتفية عبر رد الـwebhook. استدعها بطلب مستقل.
  • مفتاح المالك عبر HTTPS فقط: لا يمكن استدعاء setVoiceCallApiKey·deleteVoiceCallApiKey عبر sendCustomRequest. ولحذف المفتاح استخدم HTTP Bot API أو @BotFather.
  • للبوت الذي ليس له webhook ولم يستدعِ getUpdates خلال آخر 15 ثانية، يُدفع voice_call (ومنه ما يخص مكالمة أجراها البوت) مرة واحدة عبر updateBotWebhookJSON (بغض النظر عن allowed_updates، ولا يُعاد إرساله). وتصل استعلامات الأدوات عبر updateBotWebhookJSONQuery، ويُرد عليها بـ{"result": …} أو {"error": "…"} عبر bots.answerWebhookJSONQuery. ولا يصل voice_call_transcript عبر MTProto (أما transcript في تحديث الانتهاء فيصل).
  • في أخطاء RPC يكون 429 على شكل FLOOD_WAIT_n، والباقي رموز بأحرف كبيرة كما هي.