تطوير البوتات
البوتات الصوتية
عندما يتصل شخص ببوت من التطبيق يرد عليه نموذج صوتي بالذكاء الاصطناعي. كيفية التفعيل، والتعليمات والصوت، وإشعارات المكالمات، والأدوات، وخوادم MCP، ومفتاح المالك، ونص المحادثة، ووضع الصوت الخام، وApple Watch والاحتفاظ بالمحادثة — دليل لمالكي البوتات.
البوت الصوتي بوت يستطيع الناس الاتصال به هاتفياً من التطبيق. ومن يرد على المكالمة ليس شيفرة مالك البوت بل نموذج صوتي بالذكاء الاصطناعي من Pabal، ويحدد مالك البوت عبر Bot API التعليمات والتحية والصوت واللغة والحدود التي يتبعها ذلك النموذج. وفي كل مرة تبدأ مكالمة أو تنتهي يصلك التحديث voice_call، ويمكن للنموذج أثناء المكالمة أن يستدعي شيفرة البوت (الأدوات) ليبحث عن طلب أو يتحقق من حجز. وهناك أيضاً وضع الصوت الخام الذي يستلم فيه خادمك الصوت مباشرة بدلاً من نموذج الذكاء الاصطناعي. وإن كانت هذه أول مرة تنشئ فيها بوتاً فراجع أولاً إنشاء بوت.
الوضع الحالي
لا يمكن الاتصال ببوت صوتي إلا على خادم تتحقق فيه الشروط أدناه كلها. والطرق موجودة حتى على الخوادم التي عُطّل فيها الصوت، فيمكنك قراءة الإعدادات. لكن قبل أن يُفعَّل الصوت على هذا الخادم ويسمح المشغّل لذلك البوت، لا يُحفظ أي إعداد سوى التعطيل (تعطيل الصوت · تعطيل نص المحادثة · تعطيل وضع الصوت الخام · حذف مفتاح المالك · حذف خادم 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 (نموذج الذكاء الاصطناعي من 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.
- اطلب من المشغّل السماح للبوت. على الخادم الذي لا يستقبل إلا البوتات المسموح لها (الافتراضي)، إلى أن يسمح المشغّل بالبوت بمعرّفه (الرقم الذي قبل
:في الرمز المميّز) من تبويب الصوت في صفحة الإدارة، لا يظهر زر المكالمات الصوتية في @BotFather، وتغيير الإعدادات يعطي403 VOICE_NOT_AVAILABLE. وإن لم يتم الإطلاق العام بعد فاطلب أيضاً إضافة حسابك الذي ستجرّب به الاتصال إلى المُختبِرين (معرّف المستخدم الخاص بك هوmessage.from.idفيgetUpdatesبعد أن ترسل رسالة إلى البوت). - حدّد التعليمات. وهي نص يصف ما يفعله البوت في المكالمة. اضبطها بـ
setVoiceCallSettingsأدناه، أو أرسلها عبر زر التعليمات (지시문) في شاشة المكالمات الصوتية لدى @BotFather. ولا يمكن التفعيل من دون تعليمات (باستثناء وضع الصوت الخام). - في @BotFather:
/mybots← البوت ← المكالمات الصوتية (음성 통화) ← تشغيل (켜기). في المرة الأولى تظهر قواعد البوتات الصوتية، وعند الضغط على الموافقة على القواعد والتشغيل (규칙에 동의하고 켜기) تُفعَّل. ومن المرة التالية لا يسأل إلا «هل أشغّلها؟» (켤까요?). - انتظر السماح مجدداً. على الخادم الذي لا يستقبل إلا البوتات المسموح لها، يعني السماح أنه سُمح بالتعليمات والتحية والأدوات كما كانت وقتها، فإذا كتبت التعليمات بعد السماح تصبح الحالة
unavailable_reason: "awaiting_review". ولا يستقبل البوت المكالمات حتى يراجعها المشغّل ويسمح بها مجدداً. ويتكرر ذلك كلما غيّرت أحد هذه الثلاثة (أو عنوان الصوت الخام في وضع الصوت الخام). وإذا لم تكن لغة البوت الكورية أو الإنجليزية فلا يستقبل المكالمات إلا بعد أن يقرأ المشغّل التعليمات والتحية ويسمح له، حتى على خادم يستقبل كل البوتات (الأصوات واللغات). - تحقّق من أن
availableفيgetVoiceCallSettingsهوtrue، ثم جرّب الاتصال بزر الاتصال في الملف الشخصي للبوت من إصدار تطبيق يتضمن نافذة التأكيد.
يرد BotFather حالياً باللغة الكورية. في المحادثة أعلاه، بعد أن تضغط تشغيل يعرض BotFather قواعد البوتات الصوتية:
- يفصح البوت دائماً عن أنه ذكاء اصطناعي. ولا يمكن جعله يتظاهر بأنه إنسان.
- لا يجوز انتحال صفة جهات أو شركات أو أشخاص حقيقيين.
- لا يجوز جعله يطلب أرقام البطاقات أو أرقام الهوية الوطنية أو كلمات المرور أو رموز التحقق.
- لا يخزّن Pabal صوت المكالمة، ويُرسل الصوت إلى Google (Vertex AI) لتوليد الردود.
- لا يُسمح بالمكالمات الصوتية مع البوت إلا لمن هم في التاسعة عشرة أو أكثر.
- إذا خُرقت القواعد يمكن للمشغّل إيقاف المكالمات الصوتية أو البوت.
وبعد أن تضغط الموافقة على القواعد والتشغيل يعلن 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}
| المعامل | النوع | الوصف |
|---|---|---|
enabled | Boolean | التشغيل · الإيقاف. للتشغيل يجب أن تكون قد وافقت على القواعد لدى @BotFather (وإلا 403 VOICE_RULES_NOT_ACCEPTED)، وفي الوضع المُدار يجب أن توجد تعليمات. الإيقاف ممكن دائماً — تستمر المكالمات الجارية حتى نهايتها ولا تُقبل مكالمات جديدة فقط |
instructions | String | التعليمات التي يتبعها النموذج. حتى 8,192 بايت UTF-8 (نحو 4,000 حرف عربي، أو 2,700 حرف كوري). و"" تعني الحذف — ولا يمكن إفراغها لبوت مفعّل في الوضع المُدار. كلما طالت زادت تكلفة المكالمة، ويمكن للمتصل أن يستخرجها، فلا تضع فيها كلمات مرور أو مفاتيح |
greeting | String | أول ما يقوله البوت بعد إعلان الذكاء الاصطناعي. حتى 512 بايت (نحو 250 حرفاً عربياً، أو 170 حرفاً كورياً). وإذا كانت "" يحيّي النموذج باختصار من تلقاء نفسه |
voice | String | معرّف الصوت (القائمة)، الافتراضي kore |
language | String | اللغة التي يتكلم بها البوت ويفهمها، وهي واحد من 9 معرّفات (الافتراضي ko-KR، القائمة). ويُحفظ zh-CN على أنه cmn-CN، والمعرّف الذي لا يختلف إلا في الأحرف الكبيرة والصغيرة (ja-jp) يُحفظ بالشكل الوارد في القائمة. وإذا لم تكن الكورية أو الإنجليزية فلا يستقبل البوت المكالمات إلا بعد أن يقرأ المشغّل التعليمات والتحية ويسمح له. أما إعلان الذكاء الاصطناعي في بداية المكالمة فلا يتبعها، بل يأتي بلغة نافذة التأكيد التي رآها المتصل |
max_call_duration | Integer | أقصى مدة للمكالمة الواحدة (بالثواني). من 60 فأكثر، ولا تتجاوز limits.max_call_duration (الأصغر بين سقف المشغّل وسقف الخادم، الافتراضي 600). والقيمة الافتراضية للبوت 300 |
max_concurrent_calls | Integer | عدد المكالمات التي تُستقبل في الوقت نفسه. من 1 فأكثر، ولا يتجاوز limits.max_concurrent_calls (الافتراضي 2). والقيمة الافتراضية للبوت 2 |
tools | Array | أدوات البوت التي يمكن للنموذج استدعاؤها أثناء المكالمة (الأدوات). و[] تحذفها كلها |
transcript | Boolean | مفتاح تشغيل نص المحادثة (نص المحادثة) |
mode، stream_url، stream_secret | String | وضع الصوت الخام (إعداد وضع الصوت الخام) |
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_only | true إذا لم يتم الإطلاق العام بعد فلا يتصل إلا المُختبِرون الذين حدّدهم المشغّل |
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_after60).
الاستخدام — 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، وعلى اليمين شيفرة مالك البوت. والخطوط المنقطة العمودية هي الزمن. لا يتحدث النموذج مع البوت مباشرة، بل تمر كل الاستعلامات والردود عبر الخادم.
- الخانة الرمادية في ① — يفحص الخادم الوسائط أولاً: هل تطابق
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).
- إذا وُجد webhook فعبره. ويجب أن تتضمن
allowed_updatesالخاصة بـ webhook القيمةvoice_call_tool_query، وإلا فلا يذهب الاستعلام عبر طريق آخر ولا يصل (tool_delivery_problem: "not_in_allowed_updates"). والاستعلام طلب POST يُرسل منفصلاً، فلا ينحبس في قائمة الترتيب، ولا يُعاد إرساله. - إذا لم يوجد webhook وكنت قد استدعيت getUpdates خلال آخر 15 ثانية، فيأتي في مقدمة getUpdates التالي. ويجب أيضاً أن يكون في
allowed_updates. وحد الاستعلامات المنتظرة 8 للبوت الواحد. - وإلا فإن كان البوت متصلاً عبر MTProto فعبر
updateBotWebhookJSONQuery(بغض النظر عنallowed_updates). - وإن لم يتحقق شيء من ذلك فلا يصل (
tool_delivery_problem: "not_listening")، ويسمع النموذج «The tool could not be reached.» (تعذّر الوصول إلى الأداة).
الرد — answerVoiceCallToolQuery
| المعامل | الوصف |
|---|---|
tool_query_id | voice_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 باللغة الكورية. ومضمون الشروط أعلاه كما يلي، وتوافق عليها بزر الموافقة على الشروط (약관에 동의하기):
- إذا فعّلت نص المحادثة أستلم عند انتهاء المكالمة نص المحادثة الذي كتبه الذكاء الاصطناعي، وأتولى أنا معالجة النص الذي أستلمه وحمايته.
- عند الاتصال ببوت فُعّل فيه نص المحادثة، يُعلن صوتٌ عند اتصال المكالمة أولاً أن نص المحادثة سيذهب إلى مالك البوت.
- لا أحتفظ بنص المحادثة المستلم إلا بقدر الحاجة، وأحذفه إذا طلب المتصل ذلك، ولا أستخدمه لتدريب النماذج.
- إذا استخدمت مفتاح نموذجي الخاص يذهب صوت المكالمة ونصها إلى مزوّد مفتاحي بموجب عقدي. وتقع عليّ مسؤولية تلك المعالجة، وأذكر ذلك في إشعار الخصوصية الخاص بي.
- في وضع الصوت الخام يذهب صوت المكالمة إلى خادمي لا إلى نموذج الذكاء الاصطناعي في Pabal، وأنا من يستلم ذلك الصوت ويعالجه. ولا أستخدم صوت المتصل وما قاله إلا في الحدود التي يسمح بها القانون، وأحذفه حين لا أحتاج إليه.
- إذا سأل المتصل أين يقع خادمي وماذا يفعل فإنني أخبره. ويحسن أن أكتب ذلك في تعريف البوت.
- يفصح البوت دائماً عن أنه ذكاء اصطناعي، والردود يولّدها الذكاء الاصطناعي. ولا يرد إنسان بنفسه، ولا تُشغَّل تسجيلات بشرية على أنها ردود للذكاء الاصطناعي. وفي وضع الصوت الخام يشغّل Pabal في بداية المكالمة الإعلان «This is an AI voice bot. The call's audio goes to the bot developer's server.»
- لا أطلب أرقام البطاقات ولا أرقام الهوية الوطنية ولا كلمات المرور ولا رموز التحقق، وألتزم بقواعد البوتات الصوتية كما هي.
- لا يخزّن Pabal نص المحادثة، وبعد تسليمه يُحذف من الخادم خلال 24 ساعة على الأكثر. وتُحفظ المفاتيح وأسرار وضع الصوت الخام وخوادم MCP مشفّرة ولا تُسجَّل في أي مكان.
- إذا سجّلت خادم MCP، فعندما يستدعي النموذج أثناء المكالمة أداة من أدوات ذلك الخادم تذهب القيم التي قالها المتصل إلى ذلك الخادم. وتقع عليّ مسؤولية المعالجة لدى ذلك الخادم ولدى الجهات التي يحوّل إليها، وأصرّح بجهة الاستلام بصدق.
- إذا خُرقت الشروط يمكن للمشغّل إيقاف المكالمات الصوتية أو البوت.
البنود 12–24 هي ما تلتزم به عند تفعيل المكالمات من البوتات: استخدامها فقط للأغراض المعلنة التي أذِن بها المشغّل، دون إعلان؛ وذكر اسم البوت وأنه ذكاء اصطناعي في كلماته الأولى؛ وعدم وضع معلومات حساسة في context؛ وعدم محاولة الاتصال أبداً بمن هم دون 19 عاماً؛ وعدم الالتفاف على ساعات الهدوء أو التوقف المؤقت أو الحدود؛ واستخدام ما تعرفه عن المتلقّين لتلك الأغراض فقط وحذفه عند إيقاف الإذن؛ وحماية الرمز المميّز. والمكالمات التي يجريها البوت لا تأتي بنص محادثة (البند 24). وقد تتغير صياغة الشروط بعد المراجعة القانونية.
خوادم MCP
لا تعمل إلا بعد أن يجهّز المشغّل مكوّنات 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 (الخادم وبوابة الصوت)، وعلى اليمين خادم مالك البوت. لا يتحدث خادم البوت مع التطبيق ولا مع خادم 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)، وعند انتهاء المكالمة يرسل Pabalstopمع السبب ثم يغلق بالرمز 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"
- صيغة العنوان:
wss://فقط، بلا اسم مستخدم ·#· مسافات، وبأحرف ASCII فقط (وغيرها يُرمَّز بالنسبة المئوية)، ولا يتجاوز 2,048 حرفاً. والمضيف اسم فيه نقطة أو IP عام (localhostغير مقبول)، والمنفذ 443 أو من 1024 إلى 65535. وإلا400 STREAM_URL_INVALID: …. - حدود الفحص التجريبي تُحتسب لحظة الطلب: 5 مرات في الساعة لكل بوت، و10 لكل مالك، وفي الوقت نفسه 1 لكل بوت · و2 لكل مالك. وإذا تجاوزتها
429. أما الفحص الذي لم يتصل بأي مكان لأن البوابة مشغولة فلا يُحتسب. - التحقق من العنوان: إذا أشار الاسم ولو إلى عنوان واحد خاص أو محجوز أو إلى عنوان Pabal نفسه فالنتيجة
400 STREAM_URL_INVALID: the host must resolve only to public addresses. - الفحص التجريبي: تحاول البوابة الاتصال مرة واحدة (
"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 ثانية. - إذا فُعّل وضع الصوت الخام أو تغيّر العنوان عبر 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، وnonce00112233445566778899aabbccddeeff، وcall4611686018427387905، وhostbot.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.» ثم تنقطع المكالمة. وكل أنواع الرفض تنتهي بإشارة انشغال.
والشرح الموجّه لمستخدمي التطبيق في استخدام التطبيق — الاتصال ببوت صوتي.
المكالمات من البوتات
الخادم يدعمها، لكن لا يستطيع أي بوت الاتصال بأحد قبل أن يجتاز قسم 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، وبوابة الصوت مع نموذج الذكاء الاصطناعي، وتطبيق المتلقّي. الخطوط المنقّطة العمودية هي الزمن؛ والخطوط الزرقاء المتصلة هي المكالمة وهي تتقدم، والخط الرمادي هو الرد على البوت.
- الإطار الرمادي عند ① — ثلاثة فحوص: قبل الرد، جانب البوت (هل فُعّلت الميزة، ومراجعة المشغّل، والشروط، والحدود) ووجود الإذن؛ وبعد 200، جانب الشخص (ساعات الهدوء، والتوقف المؤقت، والحد اليومي والفاصل الزمني للشخص، وجهاز يمكن أن يرنّ)؛ ومرة أخرى قبل الرنين مباشرة.
- الرمز 200 عند ② يعني «استُلم» فقط. أما هل رنّ الهاتف وهل ردّ الشخص فلا تخبر بهما إلا التحديثات عند ⑧.
- ④–⑦ جانب الشخص: يظهر في الرنين «(AI)» بعد اسم البوت، وعند الرد يُشغَّل إعلان Pabal (⑥) قبل البوت. وفي كلماته الأولى (⑦) يذكر البوت اسمه وأنه ذكاء اصطناعي والغرض من المكالمة. وطوال الإطار الأخضر يمر الصوت عبر البوابة ونموذج الذكاء الاصطناعي — فهو ليس مشفّراً من طرف إلى طرف.
- الخط الأحمر المنقّط هو الأهم: الطلب الذي لم يرنّ قط بسبب جانب الشخص، والطلب الذي رنّ دون أن يردّ أحد، ينتهيان بالطريقة نفسها وفي الوقت نفسه بـ
not_answered. فلا يستطيع البوت أن يعرف هل كان الشخص نائماً أم في مكالمة أخرى.
التفعيل — @BotFather
لا يفعّل المكالماتِ من البوت إلا مالكُه عبر @BotFather، ولا يمكن ذلك بالرمز المميّز أبداً (إرسال حقول الاتصال إلى setVoiceCallSettings يعطي 400 VOICE_FIELD_NOT_AVAILABLE). يجب أن تكون المكالمات الصوتية مفعّلة أولاً. /mybots ← البوت ← المكالمات الصوتية (음성 통화) ← المكالمات من البوت (봇이 거는 전화) ← تفعيل (켜기).
يجيب 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_reasonbot). وسحب الطلب خلال 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 يوماً).
- ما يتلقاه مالك البوت (بترتيب نافذة الإذن):
- معرّف الشخص في Pabal
- هل الإذن مفعّل (تكشفه الردود على طلبات الاتصال)
- هل ردّ الشخص
- وقت بدء المكالمة ومدتها
- من أنهى المكالمة
- الأجهزة المشتركة: يمكن لأي جهاز مسجّل الدخول أن يردّ، ويذكر البوت الغرض وما أُخبر به لمن يردّ أياً كان. وعلى حاسوب مشترك ذكّر الناس بتسجيل الخروج.
- شاشة القفل: على 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 calls | startVoiceCall: غرض لم يختره البوت، أو لغة بوت غير الكورية والإنجليزية |
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، والباقي رموز بأحرف كبيرة كما هي.