تيليجرام سيرفرلس يتيح لك تشغيل كود الواجهة الخلفية لروبوتك وتطبيقاتك المصغرة مباشرة على بنية تيليجرام التحتية — لا حاجة لتوفير خوادم، ولا للحفاظ على حاويات قيد التشغيل، ولا للتفكير في التوسع. أنت تكتب وحدات جافاسكريبت عادية، وتنشرها بأمر واحد، وستقوم تيليجرام بتشغيلها في بيئة معزولة وسريعة من نوع V8 sandbox تقع بجوار واجهة برمجة تطبيقات الروبوت وقاعدة بيانات مدمجة.

إذا سبق لك ربط روبوت بخادم افتراضي خاص (VPS) أو دالة سحابية أو لوحة استضافة لمجرد الرد على أمر /start، فهذا هو الجزء الذي لم تعد بحاجة إلى القيام به.

لماذا سيرفرلس

روبوت تيليجرام هو في جوهره برنامج يتفاعل مع التحديثات. تقليديًا، كان عليك استضافة هذا البرنامج في مكان يكون دائمًا قيد التشغيل، ويمكن الوصول إليه، وآمنًا — ثم الحفاظ على هذه الحالة. يقوم تيليجرام سيرفرلس بإزالة هذه الطبقة بالكامل:

  • لا توجد بنية تحتية. لا توجد آلة لاستئجارها، أو ترقيتها، أو مراقبتها. يتم تشغيل الكود الخاص بك عند الطلب ويتوسع مع روبوتك تلقائيًا.
  • تشمل الاعتمادات. واجهة برمجة تطبيقات روبوت تيليجرام، وقاعدة بيانات مدعومة بـ SQLite، و HTTP الصادر متاحة لكل وحدة جاهزة للاستخدام — لا شيء للتثبيت، ولا اعتمادات لربطها.
  • تنفيذ سريع ومعزول. يتم تشغيل كل استدعاء في بيئة V8 isolate خفيفة الوزن، قريبة من أنظمة تيليجرام الخاصة، لذلك تكون استدعاءات واجهة برمجة تطبيقات الروبوت وقاعدة بياناتك سريعة وموثوقة.
  • سير عمل مطور حقيقي. يعيش المشروع في مجلد على جهازك تحت التحكم في الإصدار. تقوم بتحرير الملفات، ورؤية ما تغير بالضبط، والنشر بشكل ذري، وتقديم مخطط قاعدة البيانات إلى الأمام مع ترحيلات مراجعة — بالطريقة التي تعمل بها بالفعل مع كل شيء آخر.

النموذج الذهني: كيف يعمل المشروع

أنت تعمل في ثلاثة أماكن، وتتوافق هذه الأماكن بشكل نظيف مع بعضها البعض:

أين ما هو موجود هناك
مجلد المشروع الخاص بك وحدات جافاسكريبت — المخطط، الكود المشترك، معالجات التحديث، نقاط نهاية التطبيق المصغر — وإذا كان لديك واحد، فواجهة التطبيق المصغر الأمامية
السحابة النسخة المنشورة من تلك الوحدات، قاعدة بيانات روبوتك، وملفات التطبيق المصغر
واجهة سطر الأوامر tgcloud الجسر — يعرض لك الاختلافات ويزامنها

أنت لا تقوم بالاتصال عبر SSH بأي شيء. تقوم بتحرير الملفات محليًا، وتشغيل npx tgcloud push، ويأخذ النظام الأساسي الأمر من هناك. يتم التعامل مع حركة مرور روبوتك بواسطة الوحدات المنشورة؛ وتستمر قاعدة بياناتك بين الاستدعاءات.

لدى المشروع أربعة أنواع فقط من التعليمات البرمجية، كلها داخل مجلد tgcloud/:

tgcloud/
├─ endpoints/  # وظائف يستدعيها تطبيقك المصغر
├─ handlers/   # وظائف تتفاعل مع أنواع تحديثات تيليجرام
├─ lib/        # كود مشترك تستورده من أي مكان
└─ schema.js   # جداول قاعدة بياناتك

عند وصول تحديث — رسالة، ضغطة زر، استعلام مضمن — يقوم تيليجرام بتوجيهه إلى المعالج المطابق (handlers/message.js، handlers/callback_query.js، …) ويستدعي التصدير الافتراضي له. تتحدث هذه الوظيفة إلى واجهة برمجة تطبيقات الروبوت وقاعدة البيانات عبر SDK، وتعيد. هذه هي الحلقة الكاملة. يتم تجاهل التحديث الذي لا يحتوي على معالج مطابق ببساطة، لذا قم بإضافة المعالجات التي تحتاجها فقط.

التطبيق المصغر اختياري. إذا كان روبوتك يحتوي على واحد، فإن واجهته الأمامية تعيش في نفس المشروع، بجوار tgcloud/. تستضيف السيرفرلس مخرجات البناء الخاصة به وتشغل نقاط النهاية (endpoints/) التي يستدعيها. انظر واجهة التطبيق المصغر الأمامية .

عرض توضيحي سريع

إليك روبوت عرض توضيحي كامل وعامل. يرد على كل رسالة ويتذكر عدد الرسائل التي رآها من كل محادثة.

tgcloud/schema.js

import { table, integer } from 'sdk/db';

export const counters = table('counters', {
  chatId: integer('chat_id').primaryKey() ,
  seen:   integer('seen').notNull().default(0) ,
});

tgcloud/handlers/message.js

import { api, db } from 'sdk';
import { sql } from 'sdk/db';
import { counters } from '../schema.js';

export default async function (message) {
  const chatId = message.chat.id;

  // قم بإدراج العداد، أو قم بزيادته إذا كانت هذه المحادثة تحتوي بالفعل على واحد — واحصل على
  // الصف الناتج في نفس العبارة عبر .returning().
  const [row] = await db.insert(counters)
    .values({ chatId, seen: 1 })
    .onConflictDoUpdate({
      target: counters.chatId,
      set: { seen: sql`${counters.seen} + 1` },
    })
    .returning()
    .run();

  await api.sendMessage({
    chat_id: chatId,
    text: `مرحبًا! لقد رأيت ${row.seen} رسالة (رسائل) منك.`,
  });
}

انشرها:

npx tgcloud push       # تحميل الوحدات
npx tgcloud migrate    # إنشاء جدول `counters`

هذا روبوت مباشر مع حالة مستمرة ولا يوجد خادم. كل شيء فيه — api، db، DSL الخاص بـ table() — موصوف في الأقسام أدناه.

سيرفرلس هو الواجهة الخلفية العامة لروبوتات تيليجرام والتطبيقات المصغرة، وليس قالبًا لنوع واحد من التطبيقات. إنه مثالي لـ:

  • روبوتات الذكاء الاصطناعي الحوارية التي تحتاج إلى تخزين حالة لكل مستخدم في قاعدة بيانات.
  • واجهات خلفية للتطبيقات المصغرة تخزن بيانات المستخدم وتقدم محتوى ديناميكي.
  • الألعاب والأدوات — بما في ذلك لوحات الصدارة، والاختبارات، والمزيد.
  • الأتمتة والتكاملات التي تستدعي واجهات برمجة تطبيقات HTTP خارجية وتدفع النتائج إلى المحادثات.

البدء

يأخذك هذا الدليل من مجلد فارغ إلى روبوت مباشر يرد على الرسائل ويخزن البيانات. يفترض أن لديك Node.js 18 أو أحدث مثبتًا وروبوت مسجلًا مع @BotFather . بنهاية هذا الدليل، ستكون قد استخدمت كل أمر تحتاجه يوميًا: push، migrate، run، و status.

قبل أي شيء آخر، قم بتشغيل سيرفرلس. في @BotFather ، افتح روبوتك → Serverless وقم بتشغيله. هذا يشغل الميزة لروبوتك ويفتح رمز الوصول الخاص به لواجهة سطر الأوامر، والمعالجات، والمكتبة، وقاعدة البيانات.

1. إنشاء مشروع

أسرع طريقة للبدء هي منشئ المشروع، الذي ينظم هيكل مشروع ويثبت واجهة سطر الأوامر فيه:

npm create @tgcloud/bot example_bot
cd example_bot

الوسيط هو المجلد الهدف: مرر . لتنظيم الهيكل في المجلد الحالي، أو أي مسار. يعمل أيضًا في مجلد موجود ولا يتجاوز الملفات التي لديك بالفعل.

هذا يمنحك مشروعًا جاهزًا للتحرير:

example_bot/
├─ tgcloud/             # كل ما يشغله النظام الأساسي
│  ├─ handlers/
│  │  └─ message.js     # معالج رسائل مبدئي (يعكس النص مرة أخرى)
│  ├─ lib/              # وحداتك المشتركة تذهب إلى هنا (فارغة للبدء)
│  └─ schema.js         # جداول قاعدة بياناتك
├─ docs/
│  └─ tgcloud-sdk.md    # مرجع SDK (لك ولأدوات الذكاء الاصطناعي الخاصة بك)
├─ AGENTS.md            # توجيه لمساعدي البرمجة بالذكاء الاصطناعي
├─ package.json
└─ tgcloud.jsonc        # إعدادات المشروع

الملفات المنظمة ذاتية التوثيق — كل ملف منها يحتوي على أمثلة معلقة لما يمكنك القيام به بعد ذلك.

يتم تثبيت واجهة سطر الأوامر في المشروع كاعتماد تطوير محلي، لذلك تقوم بتشغيلها باستخدام npx tgcloud <command> (npx يعثر على النسخة في node_modules الخاص بمشروعك) ، أو من خلال اختصارات npm run التي يضيفها المنظم إلى package.json (npm run deploy, npm run status). افتراضيًا، لا توجد tgcloud عامة على PATH الخاص بك.

يمكنك أيضًا تثبيتها عالميًا — npm install -g @tgcloud/cli — إذا كنت تفضل كتابة tgcloud عادية من أي مكان. هذا مفيد لتشغيل tgcloud init في أي مجلد فارغ، وهذا ما تحتاجه الإكمال التلقائي للصدفة. في كلتا الحالتين، تحصل على نفس المشروع.

هل تقوم ببناء تطبيق مصغر؟ ابدأ من واجهته الأمامية بدلاً من ذلك. قم بإنشائه باستخدام أداتك المعتادة، ثم قم بتشغيل منشئ المشروع في هذا المجلد. انظر واجهة التطبيق المصغر الأمامية .

2. ربط روبوتك

كل مشروع مرتبط بروبوت واحد. قم بربطهما باستخدام login، الذي يطلب رمز الوصول الخاص بواجهة سطر الأوامر لديك ( @BotFather → روبوتك → Serverless → CLI Access → Access token — رمز مميز منفصل عن رمز الوصول الخاص بروبوتك ) ويقوم بتخزينه محليًا:

npx tgcloud login

الرمز المميز له الشكل app<id>:<secret>. تحتفظ واجهة سطر الأوامر به في .tgcloud/، والذي يتم تجاهله بواسطة git، ولا تطبع أبدًا الجزء السري. تسجيل الدخول هو المرة الوحيدة التي يُطلب منك فيها — انظر المصادقة لكيفية حل الرموز المميزة في CI.

3. استكشاف

يقدم لك أمران تقريرًا عن الوضع الحالي في أي لحظة، وكلاهما يعمل دون اتصال بالإنترنت:

npx tgcloud status     # ما الذي تغير محليًا مقابل النسخة المنشورة
npx tgcloud diff       # التغييرات سطر بسطر

مباشرة بعد init، كل شيء جديد ولم يتم نشره بعد. يعرض status الملفات المبدئية التي تنتظر التحميل.

4. النشر

أرسل وحداتك إلى السحابة:

npx tgcloud push

يقوم push بتحميل كل وحدة معدلة في دفعة ذرية واحدة ويحدث سجلك المحلي لما تحتويه السحابة الآن. روبوتك مباشر: افتحه في تيليجرام وأرسل له رسالة — المعالج المبدئي يعكسها مرة أخرى.

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

5. إضافة جدول قاعدة بيانات

دعنا نجعل الروبوت يتذكر شيئًا. افتح tgcloud/schema.js وأعلن عن جدول:

import { table, integer, text, sql } from 'sdk/db';

export const messages = table('messages', {
  id:      integer('id').primaryKey({ autoIncrement: true }) ,
  chatId:  integer('chat_id').notNull() ,
  text:    text('text') ,
  created: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`) ,
});

انشر المخطط، ثم طبقه على قاعدة البيانات:

npx tgcloud push       # تحميل المخطط الجديد
npx tgcloud migrate    # إنشاء جدول `messages`

يقوم push بالإبلاغ عن أن المخطط غير متزامن ويعرض لك التغيير المعلق، ولكنه لا يطبق أي شيء. يقوم migrate بتوجيهك خلال التغيير، عند تأكيدك، يقوم بإنشاء الجدول. هذا النموذج المكون من خطوتين — وما يحدث مع التغييرات الأكثر خطورة مثل الحذف — مغطى في الترحيلات .

6. تخزين وقراءة البيانات

الآن استخدم الجدول من المعالج الخاص بك. قم بتحرير tgcloud/handlers/message.js:

import { api, db } from 'sdk';
import { eq } from 'sdk/db';
import { messages } from '../schema.js';

export default async function (message) {
  // حفظ هذه الرسالة.
  await db.insert(messages)
    .values({ chatId: message.chat.id, text: message.text })
    .run();

  // عد عدد الرسائل التي قمنا بتخزينها لهذه المحادثة.
  const count = await db.$count(messages, eq(messages.chatId, message.chat.id));

  await api.sendMessage({
    chat_id: message.chat.id,
    text: `تم الحفظ. هذا هو ${count} رسالة (رسائل) من هذه المحادثة حتى الآن.`,
  });
}

انشر المعالج المحدث باستخدام npx tgcloud push، ثم أرسل لروبوتك بضع رسائل وشاهد العداد يتزايد. تستمر قاعدة البيانات بين الاستدعاءات — هذه هي ذاكرة روبوتك.

7. الاختبار دون نشر

لست بحاجة إلى النشر لتجربة تغيير. npx tgcloud run يقوم بتنفيذ معالج على النظام الأساسي باستخدام ملفاتك المحلية، دون نشرها:

npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hello" }'

الوسيط هو الحمولة التي يتلقاها المعالج — لـ handlers/message، رسالة — مكتوبة بتنسيق JSON5 (لذلك يمكنك تخطي اقتباس المفاتيح). الأمر يطبع أي شيء سجله المعالج باستخدام console.*، وقيمة الإرجاع، والوقت الذي استغرقه. هذه هي أضيق حلقة لتكرار المنطق — لا نشر، لا انتظار رسالة حقيقية.

8. البقاء متزامنًا

أثناء عملك، تحتفظ حفنة من الأوامر بمشروعك المحلي والسحابة متوافقة: npx tgcloud status يعرض ما تغير، npx tgcloud push ينشر، npx tgcloud pull يجلب مشروعك المحلي ليصبح متوافقًا مع السحابة، npx tgcloud fetch يتحقق مما تم نشره دون لمس ملفاتك، و npx tgcloud reset يتخلص من التغييرات المحلية.

إذا قام شخصان (أو جهازان) بالنشر لروبوت واحد، يكتشف النظام الأساسي التعارض ويتوقف push للسماح لك بـ pull أولاً — لا يمكنك أبدًا الكتابة فوق عمل شخص آخر بصمت. انظر البقاء متزامنًا .

البناء باستخدام الذكاء الاصطناعي

تفضل البناء بمساعد ذكاء اصطناعي — أو هل المبرمج الوحيد في فريقك هو الذكاء الاصطناعي؟ لا يزال بإمكانك شحن روبوت. لقد اتخذنا خطوة أولى لجعل وكيل الذكاء الاصطناعي يشعر بالراحة في مشروع: كل مشروع جديد يتم تنظيمه مع ملف AGENTS.md ومرجع docs/tgcloud-sdk.md الذي تقرأ أدوات ترميز الوكلاء تلقائيًا.

جنبًا إلى جنب مع بيئة تشغيل صغيرة ومكتفية ذاتيًا — SDK واحد، لا حزم npm للتعامل معها — هذا يعطي المساعد بداية سريعة في الاتفاقيات التي يميل الترميز العام إلى تفويتها هنا: استيرادات نسبية مع .js، لا مفاتيح أجنبية، كل استدعاء db غير متزامن، معالج واحد لكل نوع تحديث، وتدفق push/migrate المكون من خطوتين.

جربها:

npm create @tgcloud/bot my-bot
cd my-bot
opencode            # أو Claude Code، Cursor، … — أي وكيل يقرأ AGENTS.md

ثم فقط اسأل، بلغة عادية:

اكتب روبوتًا يتذكر قائمة مهام كل شخص — أضف عنصرًا عندما يرسل نصًا، واعرض القائمة بأكملها عندما يرسل /list.

يقوم المساعد بتحرير schema.js والمعالجات نيابة عنك؛ تقوم بمراجعة، واختبار تغيير على الفور باستخدام npx tgcloud run، ثم التشغيل المباشر باستخدام npx tgcloud push و npx tgcloud migrate. AGENTS.md هو جزء من مشروعك — قم بتحريره مع نمو الروبوت لتبقى الإرشادات دقيقة.

أثناء التنقل مع BotFather

هل أنت مقتصر على هاتفك؟ يعيش المشروع بأكمله في @BotFather أيضًا — افتح روبوتك → Serverless وتحصل على كل ما تديره واجهة سطر الأوامر، على شاشة لمس:

  • المعالجات — إنشاء، تحرير، وتشغيل اختبار لمعالجات التحديث؛ يحافظ BotFather على تزامن الخطاف مع المعالجات التي لديك (نفس حالة متزامن / غير متزامن التي يقدمها CLI).
  • نقاط النهاية — الوظائف التي يستدعيها تطبيقك المصغر.
  • المكتبة — وحدات lib/ المشتركة الخاصة بك.
  • قاعدة البيانات — حرر schema.js بنفس صيغة Drizzle-like، راجع التغييرات المعلقة، وطبقها؛ حفظ ينشر.
  • ملفات التطبيق المصغر — الواجهة الأمامية التي يفتحها المستخدمون في تيليجرام.
  • وصول واجهة سطر الأوامر — احصل على رمز الوصول الخاص بواجهة سطر الأوامر هنا عندما تعود إلى لوحة مفاتيح.

إنه نفس مشروع السحابة، لذا يمكنك بدء معالج على هاتفك و npx tgcloud pull على جهاز الكمبيوتر المحمول الخاص بك لاحقًا — لا شيء مرتبط بعميل واحد. تشغيل معالج يعرض حتى مخرجات وحدة التحكم الخاصة به مباشرة في المحادثة، تمامًا مثل npx tgcloud run .

المشاريع والوحدات

مشروع سيرفرلس هو مجلد عادي تحت التحكم في الإصدار. كل ما يشغله النظام الأساسي يقع في مجلد واحد، tgcloud/؛ بقية المشروع هو لك — واجهة أمامية لتطبيق مصغر، إعدادات، وثائق. لا توجد خطوة بناء للروبوت، ولا node_modules في وقت التشغيل، ولا نقطة دخول للخادم.

بنية المشروع

example_bot/
├─ tgcloud/             # الجزء المنشور — لا شيء آخر
│  ├─ handlers/         # معالجات التحديث — مسطحة، مستوى واحد فقط
│  │  ├─ message.js
│  │  └─ callback_query.js
│  ├─ endpoints/        # وظائف يستدعيها تطبيقك المصغر — مسطحة، مستوى واحد فقط
│  │  └─ getProfile.js
│  ├─ lib/              # وحدات مشتركة؛ المجلدات الفرعية مسموحة
│  │  ├─ reply.js
│  │  └─ internal/util.js
│  └─ schema.js         # مخطط قاعدة البيانات — ملف واحد
└─ .tgcloud/            # حالة CLI، تم تجاهلها بواسطة git

فقط ملفات .js داخل tgcloud/ — schema.js، lib/، handlers/، endpoints/ — يتم نشرها. كل شيء آخر — Markdown، ملفات الإعداد، مجلد .tgcloud/، أي شيء خارج tgcloud/ — يبقى على جهازك. أسماء الوحدات نسبية إلى tgcloud/. الملف tgcloud/handlers/message.js هو الوحدة handlers/message، وهذا هو الاسم الذي يستخدمه npx tgcloud run.

الترقية من مشروع أقدم؟ المشاريع التي تم إنشاؤها قبل CLI 0.2.0 احتفظت بـ schema.js، lib/ و handlers/ في جذر المشروع. قم بتشغيل npx tgcloud upgrade مرة واحدة لنقلها إلى tgcloud/.

schema.js — قاعدة بياناتك. يعلن عن الجداول كتصديرات مسماة باستخدام DSL المخطط ويعيش في tgcloud/ كملف واحد. يتم نشره مثل أي وحدة أخرى، ولكن نشره لا يغير قاعدة البيانات أبدًا — يتم تطبيق تغييرات المخطط بشكل منفصل باستخدام npx tgcloud migrate. انظر قاعدة البيانات .

lib/ — كود مشترك، أي شيء تريد إعادة استخدامه عبر المعالجات: أدوات مساعدة خالصة، طبقات وصول إلى قاعدة البيانات، تنسيق، تكامل مع خدمات خارجية. lib/ هو الدليل الوحيد الذي قد يحتوي على مجلدات فرعية (lib/internal/util.js، lib/payments/stripe.js) ، لذا يمكنك تنظيم قاعدة كود أكبر كيفما تشاء. الوحدات في lib/ لا يتم استدعاؤها مباشرة بواسطة النظام الأساسي؛ إنها موجودة ليتم استيرادها بواسطة المعالجات والبعض الآخر.

handlers/ — نقاط الدخول لروبوتك. كل ملف يتوافق مع نوع تحديث تيليجرام واحد، ويقوم النظام الأساسي بتوجيه كل تحديث وارد إلى المعالج المطابق:

ملف يتعامل مع
handlers/message.js الرسائل الواردة الجديدة
handlers/inline_query.js الاستعلامات المضمنة الواردة الجديدة
handlers/callback_query.js استعلامات رد الاتصال الواردة الجديدة
… أي نوع تحديث آخر من Bot API

handlers/ مسطح — لا توجد مجلدات فرعية. التصدير الافتراضي للمعالج (export default) هو الوظيفة التي يستدعيها النظام الأساسي (انظر المعالجات ).

يتم التعامل مع نوع التحديث فقط إذا كان ملف المعالج الخاص به موجودًا وغير فارغ. إذا لم يكن هناك handlers/<type>.js — أو كان الملف فارغًا — يتم تجاهل التحديثات من هذا النوع، ويقوم النظام الأساسي بتشغيل لا شيء لها. لذا احتفظ فقط بالمعالجات التي تحتاجها بالفعل: كل معالج تضيفه هو نوع تحديث آخر يستيقظ روبوتك لمعالجته، وترك الباقي يعني أن تيليجرام لا يقوم بتشغيل الكود الخاص بك للتحديثات التي ستتخلص منها على أي حال.

لتنظيم معالج جديد، قم بتشغيل npx tgcloud add handlers/<type> .

endpoints/ — الجانب الخلفي لتطبيقك المصغر. كل ملف هو وظيفة يستدعيها التطبيق المصغر بالاسم، لذا endpoints/getProfile.js يرد على POST /api/getProfile. يتحقق النظام الأساسي من أن المكالمة تأتي من مستخدم تيليجرام داخل تطبيقك المصغر قبل تشغيل الكود الخاص بك، ويسلمك هذا المستخدم. endpoints/ مسطح، مثل handlers/. انظر نقاط النهاية .

.tgcloud/ — حالة محلية للجهاز تديرها واجهة سطر الأوامر بالكامل، بما في ذلك بيانات الاعتماد المحفوظة الخاصة بك. يتم تجاهلها بواسطة git، ويجب عليك أبدًا عدم القراءة منها أو الكتابة إليها يدويًا — استخدم أوامر واجهة سطر الأوامر بدلاً من ذلك.

نظام الوحدات

في وقت التشغيل، يمكن للوحدة رؤية شيئين بالضبط: SDK النظام الأساسي و الوحدات الأخرى في مشروعك. لا توجد حزم npm، ولا نظام ملفات، ولا شبكة إلا من خلال fetch الخاص بـ SDK.

قم باستيراد وحداتك الخاصة بالطريقة التي تفعلها في أي مشروع JavaScript — عن طريق المسار النسبي، مع امتداد .js — و SDK باسمه:

// tgcloud/handlers/message.js
import { users } from '../schema.js';              //  وحدة المخطط
import { addItem } from '../lib/cart.js';          //  وحدة مكتبة
import { format } from '../lib/internal/fmt.js';  //  وحدة مكتبة متداخلة
import { db, api, fetch } from 'sdk';              //  SDK النظام الأساسي
// tgcloud/lib/cart.js
import { format } from './internal/fmt.js';        //  جارة
import { users } from '../schema.js';

يجب أن ينتهي الاستيراد النسبي بـ .js ولا يمكن أن يؤدي خارج tgcloud/:

import { users } from '../schema';       //  أضف .js
import config from '../../config.js';    //  خارج tgcloud/
import { z } from 'zod';                 //  لا توجد حزم npm

كل وحدة لها أيضًا اسم — مسارها داخل tgcloud/ بدون الامتداد، مثل schema، lib/cart أو handlers/message. هذا الاسم هو ما يستخدمه npx tgcloud run ومخرجات واجهة سطر الأوامر، ويمكن استيراد الوحدة باسمها أيضًا (from 'lib/cart', بدون امتداد). فضل الاستيرادات النسبية في الكود الخاص بك. إنها ما يفهمه محرر الكود الخاص بك — الانتقال إلى التعريف، الإكمال التلقائي، إعادة التسمية.

ما هو متاح في وقت التشغيل هو شيئان بالضبط: sdk ووحداته الفرعية (sdk/db, sdk/api, sdk/fetch) — سطح النظام الأساسي بأكمله، انظر SDK — و وحداتك الخاصة في tgcloud/. هذه هي القائمة الكاملة. إذا قام الكود الخاص بك بـ import أي شيء آخر، فلن يتم حله. هذا القيد هو ما يجعل الوحدات سريعة التحميل وآمنة التشغيل.

المعالجات

المعالج هو وحدة في handlers/ يتم استدعاء تصديره الافتراضي بواسطة النظام الأساسي عندما يصل تحديث مطابق.

// handlers/message.js
import { api } from 'sdk';

export default async function (message) {
  await api.sendMessage({
    chat_id: message.chat.id,
    text: `لقد قلت: ${message.text ?? '(لا يوجد نص)'}`,
  });
}

يتلقى المعالج حمولة التحديث كمعامل له — يقوم النظام الأساسي بفك تغليف تحديث تيليجرام لك. handlers/message.js يتلقى الرسالة (أي update.message)؛ handlers/callback_query.js يتلقى استعلام رد الاتصال ؛ وهكذا. المعامل الثاني للمعالج هو كائن سياق لكل استدعاء، ctx. يحمل التحديث الخام كـ ctx.update — استخدمه عندما تحتاج إلى شيء خارج الحمولة، مثل update_id.

يمكن أن يكون المعالج async (عادة ما يكون كذلك) وقد يعيد قيمة. يصل إلى Bot API وقاعدة البيانات و HTTP الصادر عبر SDK.

لا تحتاج إلى تحديث حقيقي لاختبار معالج. npx tgcloud run يقوم بتنفيذه على النظام الأساسي مع الحمولة التي تزودها والكود المحلي الحالي الخاص بك:

npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'

الوسيط هو الحمولة — نفس الكائن الذي يتلقاه المعالج — بتنسيق JSON5. لتوفير ctx للمعالج (معامله الثاني)، أضف --ctx، على سبيل المثال --ctx '{ update: { update_id: 1 } }'. هذا يعمل ضد ملفاتك المحلية، لذا يمكنك تجربة التغييرات قبل نشرها. انظر run .

نقاط النهاية

نقطة النهاية هي وحدة في endpoints/ يستدعيها تطبيقك المصغر — المكان الخاص بأي شيء لا يمكن للواجهة الأمامية القيام به بمفردها، مثل قراءة وكتابة قاعدة البيانات، أو استدعاء Bot API، أو التحدث إلى خدمات خارجية بأسرارك.

// endpoints/getProfile.js
import { db, EndpointError } from 'sdk';
import { eq } from 'sdk/db';
import { profiles } from '../schema.js';

export default async function (input, ctx) {
  const user = ctx.initData.user;
  const profile = await db.select().from(profiles).where(eq(profiles.userId, user.id)).get();
  if (!profile) throw new EndpointError('Profile not found', { userId: user.id });
  return profile;
}

الوسيط الأول، input، هو كائن JSON الذي أرسله التطبيق المصغر. الثاني، ctx، يحمل ctx.initData — بيانات التهيئة للتطبيق المصغر (user، chat، start_param، auth_date، …) ، تم التحقق منها بالفعل بواسطة النظام الأساسي. لها نفس شكل Telegram.WebApp.initDataUnsafe على العميل، ولكن هنا يمكنك الوثوق بها. يتم رفض المكالمة التي لا تحتوي على بيانات تهيئة صالحة مع 401 قبل تشغيل الكود الخاص بك، لذا فإن ctx.initData.user هو حقًا الشخص الذي فتح تطبيقك المصغر. يمكن استدعاء نقاط النهاية بهذه الطريقة فقط — من تطبيقك المصغر الخاص بالروبوت، بواسطة مستخدم تيليجرام — لذا لا توجد واجهة برمجة تطبيقات عامة لحمايتها ولا تسجيل دخول لبنائه.

كل ما تعيده نقطة النهاية يتم إرساله مرة أخرى بتنسيق JSON. لرفض مكالمة، throw new EndpointError(description, parameters?). يحصل التطبيق المصغر على الوصف و parameters كـ هما. إذا كنت بحاجة إلى التفرع حسب نوع الخطأ في الواجهة الأمامية، فضع رمزًا في parameters — { code: 'OUT_OF_STOCK' }. يتم الإبلاغ عن أي استثناء آخر كخطأ داخلي عام، وتذهب الرسالة الحقيقية فقط إلى سجلاتك، لذا لا تتسرب التفاصيل الداخلية إلى العميل.

استدعاء نقطة نهاية من التطبيق المصغر

يستدعي التطبيق المصغر نقطة نهاية بالاسم باستخدام Telegram.WebApp.Serverless.call من SDK الخاص بالتطبيقات المصغرة الرسمي، telegram-web-app.js — البرنامج النصي الذي يضمنه تطبيقك المصغر بالفعل. يرسل بيانات التهيئة نيابة عنك ويستدعي مرة أخرى خطأ أو نتيجة نقطة النهاية:

Telegram.WebApp.Serverless.call('getProfile', { lang: 'en' }, function (err, profile) {
  if (err) {
    // err.message — الوصف؛ err.parameters — ما حمله EndpointError الخاص بك
    if (err.type === 'ENDPOINT_ERROR') showMessage(err.message);
    return;
  }
  render(profile);
});

المدخلات اختيارية ويجب أن تكون كائنًا عاديًا. بدونها، تكون المكالمة Serverless.call('getProfile', callback).

err.type هو 'ENDPOINT_ERROR' عندما يرمي المعالج EndpointError. أي قيمة أخرى تعني أن النظام الأساسي رفض المكالمة أو فشل المعالج بشكل غير متوقع، على سبيل المثال 'UNAUTHORIZED' عندما تكون بيانات التهيئة مفقودة أو غير صالحة.

أسماء نقاط النهاية مثل معرفات JavaScript — حروف، أرقام و _، لا تبدأ برقم (getProfile، place_order). قم بتنظيم واحدة باستخدام npx tgcloud add endpoints/<name> ، وجربها بدون التطبيق المصغر باستخدام run ، مع تمرير بيانات التهيئة بنفسك:

npx tgcloud run endpoints/getProfile '{}' --ctx '{ initData: { user: { id: 1, first_name: "Ann" } } }'

ما الذي يتم نشره

عندما تقوم بتشغيل npx tgcloud push، تجمع واجهة سطر الأوامر كل ملف .js تحت schema.js، lib/، handlers/، و endpoints/، وترسل هذا النطاق الدقيق كمساحة وحدات مشروعك — جنبًا إلى جنب مع مجلد بناء التطبيق المصغر، إذا سمى tgcloud.jsonc واحدًا (انظر أدناه). أي شيء موجود في السحابة وغائب عن مشروعك يتم إزالته، لذا فإن الحالة المنشورة تعكس دائمًا مجلدك — بما في ذلك عمليات الحذف. لا شيء خارج tgcloud/ يتم نشره ككود — بقية جذر المشروع هو لك. داخل tgcloud/، يتم تمييز ملف .js الذي لا يقع في أحد هذه المواقع (tgcloud/util.js، tgcloud/helpers/x.js)، لذلك لا يتم نشره بصمت. لا يتم نشر Markdown، أو ملفات النقاط، أو .tgcloud/ أبدًا.

واجهة التطبيق المصغر الأمامية

جذر المشروع هو ملكك، وهو المكان الطبيعي للواجهة الأمامية لتطبيقك المصغر — مبنية باستخدام React، Vue، Svelte أو HTML عادي، بواسطة Vite أو أي مجمع آخر. تستضيف سيرفرلس مخرجات البناء بجوار روبوتك. نشرة واحدة push تنشر الكود والملفات الثابتة معًا، ويتم تقديم التطبيق على https://app<app_id>.tgcloud.ai/ (يطبع push العنوان الدقيق — استخدمه كعنوان URL لتطبيقك المصغر في @BotFather ).

الإبلاغ عن الإساءة. يتم نشر التطبيقات المصغرة على tgcloud.ai بواسطة مطورين مستقلين. للإبلاغ عن محتوى غير قانوني أو إساءة استخدام على نطاق فرعي app<app_id>.tgcloud.ai، أرسل بريدًا إلكترونيًا إلى [email protected] .

للبدء، قم بإنشاء الواجهة الأمامية أولاً، باستخدام أي أداة تفضلها، ثم أضف سيرفرلس فوقها:

npm create vite@latest example_app   # أو create-vue، …
cd example_app
npm create @tgcloud/bot .

لا يتجاوز منشئ المشروع ملفاتك أبدًا. يضيف مجلد tgcloud/ بجوار الواجهة الأمامية، ويكتشف مجلد البناء، ويضيف نص npm run deploy الذي يبني التطبيق وينشره مع الروبوت.

ما يتم استضافته يتم تحديده في tgcloud.jsonc، ملف إعدادات المشروع في جذر المشروع:

{
  "$schema": "./node_modules/@tgcloud/cli/schema/tgcloud.json",
  "static": {
    "source": "dist",
    "spa": true,
    "immutable": ["/assets/*"],
    "headers":   [{ "path": "/*", "set": { "Referrer-Policy": "no-referrer" } }],
    "redirects": [{ "from": "/old", "to": "/new", "status": 301 }]
  }
}

"source" مطلوب فقط. إنه مجلد البناء، نسبيًا إلى جذر المشروع. المفاتيح الأخرى لـ "static" تهيئة كيفية تقديمه (أدناه). init ينشئ tgcloud.jsonc لك. عندما يكتشف الواجهة الأمامية، فإنه يملأ أيضًا "source":

الواجهة الأمامية init يضبط "source" على
Vite — React، Vue، Svelte، Solid، … dist
Next.js، Nuxt، SvelteKit، React Router، TanStack Start، Angular لا شيء — قم بتكوين تصدير ثابت أولاً، ثم اضبط "source" على مجلده بنفسك

مع أي إعداد آخر، اضبط "source" على مجلد البناء الخاص بك بنفسك.

السطر $schema يمنح محرر الكود الخاص بك الإكمال التلقائي والتحقق من الصحة لكل مفتاح. الملف هو JSON مع تعليقات وفواصل لاحقة مسموح بها. إذا اضطر أداة أخرى لديك لقراءته كـ JSON عادي، فسميه tgcloud.json بدلاً من ذلك — عندها يجب أن يكون JSON صارمًا.

البناء قبل الدفع. push يحمل المجلد بالضبط كما هو على القرص — باستثناء الملفات والمجلدات التي يبدأ اسمها بنقطة، والتي يتم تخطيها — ولا يقوم أبدًا بتشغيل البناء الخاص بك، لذا فإن npx tgcloud push عادي بعد تحرير الواجهة الأمامية سيقوم بنشر البناء السابق. استخدم npm run deploy (يقوم بتشغيل نص build الخاص بك، ثم tgcloud push)، أو قم بالبناء بنفسك أولاً.

خيارات التقديم:

  • spa — تقديم index.html للمسارات التي لا توجد ولا تحتوي على امتداد ملف، لذا يعمل موجه جانب العميل عند إعادة التحميل. لا تزال المسارات ذات الامتداد تحصل على 404.
  • immutable — أنماط للملفات ذات تجزئة المحتوى (ملفات Vite /assets/*) التي قد تخزنها المتصفحات لمدة عام. استخدمها فقط للملفات ذات تجزئة المحتوى. أي ملف آخر مطابق هنا سيظل قديمًا في المتصفحات لمدة عام، ولا توجد طريقة لاسترجاعه.
  • headers — رؤوس الاستجابة للمسارات التي تطابقها قاعدة (مسار مطابق، أو بادئة تنتهي بـ /*). قيمة null تزيل رأسًا.
  • redirects — قواعد إعادة التوجيه، حيث يفوز التطابق الأول. الملف الموجود يتفوق دائمًا على القاعدة. status هو 301 ما لم تقم بتعيينه.
  • "static": "dist" — اختصار لـ { "source": "dist" } عندما لا تحتاج إلى أي شيء آخر.
  • "static": false — قم بإزالة الموقع المنشور عند الدفعة التالية. يؤدي حذف المفتاح بدلاً من ذلك فقط إلى إيقاف هذا المشروع من إدارة الملفات الثابتة، وسيظل ما تم نشره.

يتم تقديم الموقع من جذر المضيف الخاص به، لذا فإن base الافتراضي لمجمعك (/) صحيح. يفتح تيليجرام ويب التطبيقات المصغرة في إطار <iframe>، لذا لا تقم بإضافة X-Frame-Options أو سياسة frame-ancestors مقيدة.

نفس المضيف يستجيب لنقاط النهاية الخاصة بك (endpoints/) تحت /api/ — هذا هو المكان الذي يذهب إليه Telegram.WebApp.Serverless.call('getProfile', …). لا تضع ملفاتك الخاصة تحت /api/ في مجلد البناء.

قاعدة البيانات

يحصل كل روبوت على قاعدة بيانات خاصة به — مخزن مدعوم بـ SQLite يستمر بين الاستدعاءات ومتاح لكل وحدة عبر db. تصف جداولك في schema.js باستخدام DSL صغير ومكتوب، وتقرأها وتكتبها باستخدام منشئ استعلامات مرن، وتطورها بتترحيلات مراجعة.

هل تعرف Drizzle ORM ؟ إذن أنت تعرف بالفعل كيفية التحدث إلى قاعدة البيانات هنا. يتبع DSL المخطط ومنشئ الاستعلام Drizzle — بناة الأعمدة، select().from().where()، العوامل، onConflictDoUpdate، .returning()، وعلامة sql كلها تتصرف بالطريقة التي تتوقعها، لذا فإن قراءة البيانات وكتابتها هي واجهة برمجة تطبيقات مألوفة تستخدمها بالفعل. أنت فقط تستوردها من sdk/db، ويتم الإشارة إلى عدد قليل من مواصفات النظام الأساسي (أهمها لا توجد مفاتيح أجنبية ) حيث تظهر.

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

الجداول هي تصديرات مسماة في schema.js. استدعاء table() يبني وصفًا عند تحميله (لا يمس قاعدة البيانات)؛ يكتشف النظام الأساسي الجداول المصدرة عند نشر schema.js ويقوم بترحيل قاعدة البيانات لتتوافق معها.

import { table, integer, text, boolean, json, index, sql } from 'sdk/db';

export const users = table('users', {
  id:      integer('id').primaryKey({ autoIncrement: true }) ,
  tgId:    integer('tg_id').unique() ,
  name:    text('name').notNull() ,
  lang:    text('lang').default('en') ,
  isAdmin: boolean('is_admin').default(false) ,
  prefs:   json('prefs') ,
  created: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`) ,
}, (t) => ({
  createdIdx: index('idx_users_created').on(t.created) ,
}));

table(name, columns, extras?):

  • name هو اسم جدول SQL؛
  • columns هو خريطة لممتلكات JS → تعريف العمود؛
  • extras هو رد اتصال اختياري (t) => ({ … }) حيث يعرض t الأعمدة (t.created هو مرجع لهذا العمود) — قم بتعريف الفهارس والقيود على مستوى الجدول هنا.

أنواع الأعمدة

المصنع نوع SQLite ملاحظات
text() TEXT
integer() INTEGER
real() REAL اختصار float()
numeric() NUMERIC
blob() BLOB يقرأ/يكتب Uint8Array
boolean() INTEGER مخزن كـ 0/1، يقرأ كـ true/false
json() TEXT JSON.stringify / JSON.parse تلقائيًا

اسم العمود اختياري — قم بتخطيها واستخدم مفتاح JS. يتحكم خيار mode في كيفية تحويل القيم بين SQLite و JavaScript.

mode مخزن كـ قيمة JS
boolean INTEGER 0/1 boolean
json TEXT (JSON) أي كائن/مصفوفة
timestamp INTEGER (ثواني يونكس) Date
timestamp_ms INTEGER (أجزاء ثانية يونكس) Date
bytes BLOB Uint8Array

boolean() و json() هي اختصارات لـ integer(name, { mode: 'boolean' }) و text(name, { mode: 'json' }).

blob() يقرأ ويكتب Uint8Array — وقت التشغيل لا يحتوي على Node Buffer (و Buffer هو فئة فرعية من Uint8Array، لذا هذا هو نوع الأساس القابل للنقل). mode أعلاه يحكم فقط كيفية ترميز القيمة، بغض النظر عن نوع تخزين العمود: لذا فإن blob('col', { mode: 'json' }) هو نظير BLOB لـ json() — نفس ترميز JSON، مخزن في عمود BLOB بدلاً من TEXT.

الخلاصة: blob() يستخدم Uint8Array. mode يتحكم في الترميز، وليس التخزين، لذا فإن blob(..., { mode: 'json' }) يخزن JSON كـ BLOB، بينما json() يخزنه كـ TEXT.

معدلات الأعمدة

معدلات الأعمدة تتسلسل إلى عمود.

integer('id').primaryKey({ autoIncrement: true })
text('name').notNull()
text('tg').unique()
text('lang').default('en')
integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`)
text('slug').generatedAlwaysAs(sql`lower(name)`, { mode: 'stored' })  // أو 'virtual'
text('email').deprecated('replaced by login')   // يضع علامة على العمود للإزالة

.default() على عمود json() يقوم بترميز القيمة لك؛ .default() sql…``يتم تمريره حرفيًا..deprecated()` نهائي — انظر الترحيلات .

الفهارس والقيود

يتم تعريف الفهارس والقيود في رد الاتصال extras، حيث تكون الأعمدة في النطاق.

table('t', { /* … */ }, (t) => ({
  uq:     unique('uq_email').on(t.email) ,
  chk:    check('chk_done', sql`${t.done} in (0, 1)`) ,
  idx:    index('idx_name').on(t.col) ,
  uidx:   uniqueIndex('uidx_email').on(t.email) ,
  lower:  index('idx_lower').on(sql`lower(${t.email})`) ,          // فهرس تعبيري
  active: index('idx_active').on(t.userId).where(sql`done = 0`) ,  // فهرس جزئي
}));

المعدلات على مستوى الجدول تتسلسل بعد table(...): .strict()، .withoutRowid()، .deprecated('reason').

لا مفاتيح أجنبية

يتم تشغيل وقت التشغيل مع PRAGMA foreign_keys متوقف. سيكون المفتاح الأجنبي المعرف صامتًا وخاملًا — لا توجد تتابعات، لا حماية للأيتام — وهذا أسوأ من عدم وجوده على الإطلاق، لذا فإن DSL يجعل ذلك مستحيلاً. تحديدًا، .references() و foreignKey() على مستوى الجدول يُلقيان خطأ عند تعريفهما، ولن يتم نشر مخطط يستخدمهما.

يجب عليك نمذجة العلاقات بأعمدة عادية (userId: integer('user_id')) وإنفاذ السلامة في كود التطبيق الخاص بك: أدرج الأصول قبل الفروع، واحذف الفروع قبل الأصول، وتعامل مع الأخطاء بشكل صحيح وقم بمسح الأيتام باستخدام LEFT JOIN … WHERE parent.id IS NULL عندما تحتاج إلى ذلك.

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

الاستعلام

db هو منشئ استعلامات مرن. كل استعلام غير متزامن — الأساليب الطرفية (.all()، .get()، .values()، .run()) تعيد Promises، لذا دائمًا await.

import { db } from 'sdk';
import { eq, and, desc, asc, count, sql } from 'sdk/db';
import { users, todos } from '../schema.js';

await db.select().from(todos).all();                          // كل الصفوف
await db.select().from(todos).where(eq(todos.id, 1)).get();   // الصف الأول أو null
await db.select().from(todos).values();                       // الصفوف كمصفوفات قيم

await db.select().from(todos)
  .where(and(eq(todos.userId, uid) , eq(todos.done, false)))
  .orderBy(desc(todos.priority) , asc(todos.id))
  .limit(10).offset(20)
  .all();

// إسقاط مخصص: { alias: columnRef | sqlExpr | aggregate }
await db.select({ id: todos.id, title: todos.text, n: count() })
  .from(todos).groupBy(todos.userId).having(sql`count(*) > ${1}`).all();

// عدد الصفوف — مساعد، وليس طرف بناء:
await db.$count(todos);                        // كل الصفوف
await db.$count(todos, eq(todos.done, false)); // مع عامل تصفية
  • متسلسل: .where()، .orderBy()، .limit()، .offset()، .groupBy()، .having()، .distinct().
  • نهايات: .all()، .get()، .values().
  • عد الصفوف باستخدام db.$count(table, where?) (أو count() في إسقاط).

إدراج، تحديث، حذف:

await db.insert(todos).values({ userId: 1, text: 'Buy milk' }).run();
await db.insert(todos).values([{ text: 'A' }, { text: 'B' }]).run();   // دفعة
await db.insert(todos).values({ text: 'X' }).returning().run();        // RETURNING *

await db.insert(users).values({ tgId: 42, name: 'Ann' })
  .onConflictDoUpdate({ target: users.tgId, set: { name: 'Ann' } }).run();

await db.update(todos).set({ done: true }).where(eq(todos.id, 1)).run();
await db.delete(todos).where(eq(todos.id, 1)).run();

لاحظ أن إدراج دفعة واحدة هو عبارة واحدة، لذا فهو محدود بحد المتغيرات الخاص بـ SQLite (صفوف × أعمدة)؛ إذا تجاوزت ذلك، يفشل الإدراج — قم بتقسيمه بنفسك.

العوامل مستوردة من sdk/db:

import {
  eq, ne, gt, gte, lt, lte,
  like, notLike,
  isNull, isNotNull, and, or, not,
  between, notBetween, inArray, notInArray,
  count, sum, avg, min, max,
  asc, desc,
} from 'sdk/db';

.where(a, b) مع معاملات متعددة هو نفس and(a, b). الوسيط الثاني للمقارنة هو قيمة افتراضيًا، ولكنه قد يكون عمودًا آخر أو sql`…` — على سبيل المثال eq(a.x, b.y). التجميعات (count/sum/avg/min/max) هي أجزاء SQL لإسقاط .select({ … }).

SQL الخام — عندما لا يكون المنشئ كافيًا، قم بالنزول إلى SQL الخام. يتم اختيار الوضع حسب الطريقة: db.run للكتابات، db.all للصفوف المتعددة، db.get لصف واحد، db.values للصفوف كمصفوفات موضعية.

await db.run('UPDATE todos SET done = 1 WHERE id = :id', { ':id': 5 });
// → { rowsAffected: 3, lastInsertRowid: 0, rows: [] }

await db.all(sql`SELECT * FROM todos WHERE done = ${false}`);
await db.get(sql`SELECT count(*) AS c FROM todos`);
await db.values(sql`SELECT id, text FROM todos`);   // → [[1, 'buy milk']]

تُحل الاستعلامات الكتابية إلى نتيجة تشغيل — rowsAffected، lastInsertRowid، و rows، التي تحمل عبارة RETURNING إذا كان SQL يحتوي على واحدة. استخدم db.values عندما يقوم استعلام بإسقاط عمودين بنفس الاسم (اتصال مثل SELECT u.id, o.id): db.all يقوم بتسمية الصفوف باسم العمود، لذا فإن id الثاني سيحل محل الأول.

علامة sql`…` تحول ${value} إلى معامل مربوط و ${table.column} إلى معرف، وتدمج أجزاء sql المتداخلة. استخدم sql.raw('…') لقيمة حرفية بدون معاملات.

الاستعلامات الخام غير مرتبطة بجدول، لذا تأتي صفوفها بدون تحويل الوضع — القيمة المنطقية هي 0/1، وعمود JSON هو سلسلة نصية، والطابع الزمني هو رقم. فقط المنشئ المرتبط بالجدول يحول القيم.

الترحيلات

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

النشر لا يمس قاعدة البيانات أبدًا. عندما تقوم بتشغيل npx tgcloud push لتغيير schema.js، يسجل النظام الأساسي المخطط الجديد ويخبرك بما ستتغير عليه قاعدة البيانات — ولكنه لا يطبق شيئًا:

npx tgcloud push       # نشر schema.js؛ يبلغ عن تغييرات قاعدة البيانات المعلقة
npx tgcloud migrate    # مراجعة وتطبيقها

يقوم npx tgcloud migrate بحساب الفرق بين schema.js وقاعدة البيانات المباشرة ويقوم بتوجيهك خلالها خطوة بخطوة مع عداد شغال [N/M]:

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

ينتهي الأمر بملخص: تم تطبيقه، تم تخطيه، في انتظار إصلاح يدوي، أو غير موجود في المخطط.

انظر migrate للأعلام (--dry-run، --safe، --yes، --local).

إزالة الأشياء

حذف جدول أو عمود من schema.js لا يقوم بحذفه — هذا سيجعل الحذف العرضي كارثيًا. لإزالة شيء ما، قم بتمييزه كمهمل:

// حذف عمود
text('email').deprecated('replaced by login')

// حذف جدول كامل
export const oldSessions = table('old_sessions', { /* … */ }).deprecated('unused');

عند migrate التالي، يظهر الكائن المهمل كـ تحذير لحذف بحالة تقوم بتأكيده بشكل فردي. بمجرد إزالته، قم بإزالة التعريف.

تغيير نوع عمود

تغييرات النوع يدوية — SQLite لا يمكنها دائمًا القيام بها في المكان، وتحويل القيم الموجودة هو قرار شخصي. سيظهر migrate التغيير وسببه؛ قم بتنفيذه بنفسك باستخدام SQL خام (db.run(...)) ، عادةً عن طريق إنشاء عمود أو جدول جديد، ونسخ البيانات، والتبديل.

SDK

في وقت التشغيل، لدى الوحدة مكتبة واحدة: sdk. تجمع الأشياء الثلاثة التي تحتاجها الواجهة الخلفية لروبوت — قاعدة بيانات، Telegram Bot API، و HTTP الصادر — دون الحاجة إلى تثبيت أي شيء أو تكوين أي اعتمادات. قاعدة البيانات (db) مغطاة في قاعدة البيانات ؛ هذا القسم يغطي api، fetch، و console العام.

import { db, api, fetch, InputFile, BotApiError, EndpointError } from 'sdk';   // السطح بأكمله
// أو من وحدات فرعية:
import { table, integer, text, eq, sql } from 'sdk/db';
import { api } from 'sdk/api';
import { fetch } from 'sdk/fetch';
الاستيراد ما هو
db قاعدة البيانات — منشئ الاستعلامات و DSL المخطط → قاعدة البيانات
api Telegram Bot API — api.sendMessage(...) → أدناه
fetch HTTP الصادر → أدناه
InputFile ملف (بايتات + اسم) للتحميل — يعمل مع كل من api و fetch → ملفات
EndpointError رفض مكالمة التطبيق المصغر برسالة يمكنه عرضها → نقاط النهاية

قم باستيراد وحدات مشروعك الخاصة بالمسار النسبي مع امتداد .js (from '../schema.js', from '../lib/cart.js'). انظر نظام الوحدات .

Bot API

api يمنحك كامل Telegram Bot API . استدعِ أي طريقة باسم api.<method>(params). كل طريقة حالية — ومستقبلية — في Bot API تعمل بدون تحديث SDK مطلوب.

import { api } from 'sdk';

const me = await api.getMe();                            // → النتيجة غير المغلفة
await api.sendMessage({ chat_id: id, text: 'Hello!' });
await api.editMessageText({ chat_id, message_id, text: 'Updated' });
await api.answerCallbackQuery({ callback_query_id, text: 'Done' });

غلاف الاستجابة غير مغلف. يقوم Bot API عادةً بتغليف النتائج في { ok: true, result: … }. api يعيد result مباشرة — getMe() يحل إلى كائن المستخدم، وليس إلى غلاف. المعلمات تستخدم أسماء snake_case الخاصة بـ Bot API (chat_id، message_id، reply_markup، …).

الفشل يلقي BotApiError. عندما يعيد Bot API { ok: false }، فإن المكالمة تلقي BotApiError بدلاً من إرجاع قيمة زائفة، لذا لا يمكنك تجاهلها عن طريق الخطأ. يحمل الخطأ .code ( error_code الخاص بـ Bot API) ، .description (الرسالة التي يمكن قراءتها بواسطة الإنسان) ، .method (أي طريقة فشلت) ، و .parameters (بيانات إضافية مثل retry_after على 429، أو migrate_to_chat_id). قم بالتقاطها للتعامل مع فشل متوقع وأعد إلقاء الباقي:

import { api, BotApiError } from 'sdk';

try {
  await api.deleteMessage({ chat_id, message_id });
} catch (e) {
  if (e instanceof BotApiError && e.code === 400) {
    // 400 = الرسالة قد اختفت بالفعل؛ هذا لا بأس به هنا.
  } else {
    throw e;
  }
}

ملفات

العمل مع بايتات الملفات، وليس فقط file_ids — تحميل ملفات جديدة وتنزيل الملفات الموجودة.

تحميل — InputFile. قم بتغليف البايتات الخام في InputFile (حامل عادي: بايتات، اسم ملف، ونوع MIME اختياري type) وقم بتمريره إلى أي مكان تتوقع فيه طريقة Bot API ملفًا:

import { api, InputFile } from 'sdk';

const doc = new InputFile(bytes, 'report.pdf', { type: 'application/pdf' });
await api.sendDocument({ chat_id, document: doc });

// تعمل الملفات المتداخلة داخل المعلمات أيضًا — على سبيل المثال، ألبوم:
await api.sendMediaGroup({ chat_id, media: [
  { type: 'photo', media: new InputFile(a, 'a.jpg', { type: 'image/jpeg' }) },
  { type: 'photo', media: new InputFile(b, 'b.jpg', { type: 'image/jpeg' }) },
]});

يتم بث البايتات إلى تيليجرام كتحميل متعدد الأجزاء حقيقي — لا يتم تخزين الملف مؤقتًا بالكامل في مكالمة واحدة للنظام الأساسي — ولكن قيود Bot API الخاصة بتيليجرام لا تزال سارية: يمكن لروبوت حاليًا تحميل ما يصل إلى 50 ميجابايت لكل ملف (و 10 ميجابايت للصورة).

تنزيل — بواسطة file_id. يقوم مساعدان بسحب بايتات الملف مرة أخرى. يقوم النظام الأساسي بحل file_id لك (يقوم بتشغيل getFile ويجلب المحتوى خلف الكواليس) ، لذلك لا تلمس أبدًا رمز الروبوت أو عنوان URL للتنزيل:

const bytes = await api.getFileContent(file_id);          // → Uint8Array (الملف بأكمله)

const { file, body } = await api.getFileStream(file_id);  // بثه بدلاً من ذلك
file.file_unique_id;                                      // معلومات getFile
for await (const chunk of body) { /* Uint8Array */ }      // أو تخزينه مؤقتًا: await stream.bytes()

تفرض Bot API الخاصة بتيليجرام حدودًا على تنزيلات getFile تبلغ 20 ميجابايت، لذا هذا هو الحد الأقصى لكلا المساعدين.

HTTP

fetch هو عميل يشبه fetch لاستدعاء العالم الخارجي — واجهات برمجة تطبيقات الجهات الخارجية، خطافات الويب، أي شيء عبر HTTP.

import { fetch } from 'sdk';

const res = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Pavel' }) ,
});
if (!res.ok) throw new Error(res.statusText);
const data = await res.json();

الاستجابة تعكس منصة الويب: res.status، res.statusText، res.ok (صحيح لـ 200-299) ، res.url، res.headers (.get()، .has()، .keys()، .entries()) ، وقراء النصوص الأساسية await res.json() / await res.text().

يمكنك أيضًا قراءة النص الأساسي بشكل تدريجي كـ تدفق — for await (const chunk of res.body) { … } — وهي الطريقة التي تستهلك بها أحداث الخادم المرسلة أو الإخراج رمزًا برمز من واجهات برمجة تطبيقات الذكاء الاصطناعي.

تضبط مساعدات النص الأساسي Content-Type المطابق لك:

await fetch(url, { method: 'POST', body: fetch.body.json({ a: 1 }) }); // application/json
await fetch(url, { method: 'POST', body: fetch.body.form({ a: 1 }) }); // x-www-form-urlencoded
await fetch(url, { method: 'POST', body: fetch.body.text('hi') });     // text/plain

يمكن أن تحمل نصوص الطلبات أيضًا ملفات وبايتات خام: قم بتمرير Uint8Array، أو InputFile، أو FormData (لـ multipart/form-data) كـ body، وسيتم بث البايتات — لذا يمكنك التحميل إلى واجهة برمجة تطبيقات طرف ثالث بنفس الطريقة التي ترسل بها الملفات إلى تيليجرام:

import { fetch, FormData, InputFile } from 'sdk';

const form = new FormData();
form.append('file', new InputFile(bytes, 'photo.jpg', { type: 'image/jpeg' }));
form.append('caption', 'from my bot');
const res = await fetch('https://api.example.com/upload', { method: 'POST', body: form });

يتصرف بخلاف ذلك مثل fetch القياسي الذي تعرفه بالفعل، مع قيود:

  • يتم قراءة الاستجابة كنص — await res.json() / await res.text()، أو بثها مقطعًا تلو الآخر باستخدام res.body.
  • إجمالي الاستجابة محدود بـ 30 ميجابايت. هذا الحد يغطي الاستجابة بأكملها — البث باستخدام res.body يسمح لك بمعالجة نص أساسي كبير بشكل تدريجي، ولكنه لا يرفع الحد.

التسجيل

console العام القياسي متاح — لا شيء للاستيراد، إنه موجود فقط كما هو الحال في أي JavaScript. يتم التقاط مخرجاته وعرضها بواسطة npx tgcloud run، مما يجعله أداة التصحيح الأساسية الخاصة بك أثناء التطوير.

console.log('processing', { chatId: id });   // تسجيل / تصحيح الأخطاء — عادي
console.info('started');                     // معلومات — أزرق
console.warn('rate limited');                // تحذير — أصفر
console.error(err);                          // خطأ — أحمر، مع تتبع مكدس

يتم تمييز كل سطر بـ [file:line] الذي جاء منه. console.error و console.trace يضيفان مكدسًا كاملاً، بينما console.warn لا يفعل ذلك. عندما تقوم بتشغيل npx tgcloud run لوحدة، يتم طباعة هذه الأسطر مع بادئة ملونة لكل مستوى، والوقت منذ بدء التشغيل، والأصل — انظر run .

واجهة سطر الأوامر

tgcloud هو الجسر بين مجلد مشروعك والسحابة. يقوم بتنظيم المشاريع، ويعرض لك ما تغير، وينشر، ويشغل الوحدات، ويطبق ترحيلات قاعدة البيانات. يحتاج إلى Node.js 18 أو أحدث. طريقتان للحصول عليه:

# موصى به — قم بإنشاء مشروع مع تثبيت CLI فيه:
npm create @tgcloud/bot example_bot

# أو قم بتثبيت CLI عالميًا وبدء مجلد فارغ:
npm install -g @tgcloud/cli
tgcloud init

حزمة npm هي @tgcloud/cli، والأمر الذي تثبته هو tgcloud. تعمل كل الأوامر من أي مجلد فرعي لمشروعك.

الأمر الغرض
init تنظيم مشروع جديد في المجلد الحالي
add تنظيم وحدة جديدة (معالج، نقطة نهاية أو وحدة مكتبة)
login ربط المشروع بروبوت (يحفظ الرمز المميز)
status عرض ما تغير محليًا مقابل السحابة
diff عرض التغييرات سطر بسطر
push نشر الوحدات المتغيرة وبناء التطبيق المصغر إلى السحابة
migrate تطبيق تغييرات المخطط على قاعدة البيانات
run تنفيذ وحدة على النظام الأساسي دون نشر
fetch التحقق مما تم نشره، دون لمس الملفات المحلية
pull جلب الملفات المحلية لتتوافق مع السحابة
reset تجاهل التغييرات المحلية؛ استعادة من حالة السحابة
webhook فحص وإعادة مزامنة الخطاف الذي تديره المنصة
completion طباعة نص برمجي لإكمال الصدفة (bash/zsh/fish)
upgrade نقل تخطيط مشروع ما قبل 0.2.0 إلى tgcloud/

المصادقة

كل مشروع مرتبط بروبوت واحد بواسطة رمزه المميز، والذي له الشكل app<id>:<secret>. الجزء app<id> عام ويمكن طباعته؛ السر لا يظهر أبدًا في السجلات أو الأخطاء.

يتم حل الرمز المميز بهذا الترتيب:

  1. متغير البيئة TGCLOUD_TOKEN — لـ CI؛ لا يتم كتابته على القرص أبدًا.
  2. .tgcloud/credentials — مكتوب بواسطة npx tgcloud login .
  3. لا شيء منهما → خطأ يوجهك إلى npx tgcloud login.

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

init

npx tgcloud init

ينظم مشروعًا جديدًا في الدليل الحالي: مجلد tgcloud/ مع schema.js، lib/، handlers/ ومعالج مبدئي، بالإضافة إلى tgcloud.jsonc، AGENTS.md، docs/، ومجلد حالة .tgcloud/. قم بتشغيله في جذر مشروع واجهة أمامية موجود أيضًا — فهو يضيف مجلد tgcloud/ والوثائق، ولا يتجاوز ملفاتك أبدًا. إذا اكتشف الواجهة الأمامية، فإنه يشير أيضًا tgcloud.jsonc إلى مجلد البناء ويضيف نص deploy (البناء، ثم الدفع) إلى package.json الخاص بك — فقط إذا لم يكن لديك نص deploy بالفعل. انظر واجهة التطبيق المصغر الأمامية .

init يرفض إنشاء مشروع داخل مشروع آخر. إعادة تشغيله في جذر مشروع ما أمر طبيعي ويضيف فقط أي شيء مفقود.

add

npx tgcloud add <target>

ينظم وحدة جديدة واحدة، مترابطة وجاهزة للتحرير — معالج، نقطة نهاية أو وحدة مكتبة.

npx tgcloud add handlers/callback_query   # معالج تحديث جديد
npx tgcloud add endpoints/getProfile      # نقطة نهاية جديدة للتطبيق المصغر
npx tgcloud add lib/cart                  # وحدة مشتركة جديدة

لاحظ أن add لا تتجاوز ملفًا موجودًا أبدًا.

اسم الوحدة مطلوب. إعطاء المجلد فقط خطأ — ولكن خطأ مفيد: بالنسبة لـ handlers/، فإنه يسرد أنواع التحديثات التي لم تحصل عليها بعد، لذا يمكنك نسخ واحدة.

$ npx tgcloud add handlers
Error: Specify a name, e.g. "handlers/callback_query". Available handlers/ types: callback_query, inline_query, chat_member, …

<Tab> الإكمال يقدم نفس المجموعة — انظر completion .

الملف المنظم لديه export default حي، لذا يصبح المعالج نشطًا بمجرد نشره — لا يوجد شيء لإلغاء التعليق. انشر الوحدة الجديدة باستخدام push .

login

npx tgcloud login

يطالب بـ رمز الوصول الخاص بواجهة سطر الأوامر لديك — من @BotFather → روبوتك → Serverless → CLI Access → Access token، رمز مميز منفصل عن رمز الوصول الخاص بروبوتك — يتحقق منه مقابل النظام الأساسي، ويحفظه في .tgcloud/credentials.

login هو الأمر الوحيد الذي يطلب رمزًا مميزًا، ويتطلب طرفًا تفاعليًا. في CI، اضبط TGCLOUD_TOKEN بدلاً من ذلك.

status

npx tgcloud status

يعرض، لكل ملف، ما تغير بين دليل العمل الخاص بك والنسخة المنشورة: معدل، جديد، محذوف، لم يتغير. يعمل دون اتصال بالإنترنت، ويقارن مع حالة السحابة اعتبارًا من آخر push أو pull أو fetch. كما أنه يحذر بشأن ملفات .js متناثرة داخل tgcloud/ التي لن يتم نشرها.

لمشروع يحتوي على تطبيق مصغر، يعرض status أيضًا ما تغير في مجلد البناء الخاص به منذ آخر نشر.

diff

npx tgcloud diff

مثل status، ولكنه يعرض الاختلافات الفعلية سطر بسطر للوحدات المتغيرة. غير متصل بالإنترنت أيضًا.

push

npx tgcloud push [files...]

ينشر مشروعك إلى السحابة في دفعة ذرية واحدة.

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

مع وسيطات ملفات أو مجلدات (npx tgcloud push tgcloud/handlers/message.js، npx tgcloud push handlers/)، فإنه ينشر فقط التغييرات في تلك الملفات. الدفعة المستهدفة لا تحذف الوحدات الأخرى. يمكن كتابة الهدف كمسار الملف (tgcloud/handlers/message.js)، أو المسار داخل tgcloud/ (handlers/message.js) أو اسم الوحدة (handlers/message) — كلها تعني نفس الوحدة.

خيارها الوحيد هو --force — لتخطي فحص التزامن والكتابة فوق أي شيء في السحابة. استخدمها فقط عندما تكون متأكدًا (انظر البقاء متزامنًا ).

إذا سمى tgcloud.jsonc مجلدًا ثابتًا، فإن push كامل ينشره في نفس الدفعة — يتم تحميل الملفات المتغيرة فقط — ويطبع عنوان الموقع. قم بالبناء أولاً، لأن push لا يقوم أبدًا بتشغيل البناء الخاص بك (npm run deploy يقوم بكليهما). npx tgcloud push dist/ ينشر الملفات الثابتة وحدها، والدفعة التي تسمي الوحدات فقط تتركها دون تغيير.

بعد النشر، إذا تغير schema.js وكانت قاعدة البيانات غير متزامنة، يطبع push ملخصًا للتغييرات المعلقة ويقترح npx tgcloud migrate. لا يطبقها أبدًا بنفسه.

migrate

npx tgcloud migrate

يطبق تغييرات المخطط الخاصة بك على قاعدة البيانات. يحسب الفرق بين schema.js وقاعدة البيانات الحية ويوجهك خلالها خطوة بخطوة مع عداد شغال [N/M]:

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

ينتهي بملخص: تم تطبيقه، تم تخطيه، في انتظار إصلاح يدوي، أو غير موجود في المخطط.

انظر الترحيلات للنموذج.

الخيارات: --dry-run (طباعة كل شيء، لا تطبيق شيء)، --safe (تطبيق تلقائي للتغييرات الآمنة، تخطي التحذيرات)، --yes (تطبيق تلقائي للتغييرات الآمنة وكل التحذيرات، تخطي اليدوية — استخدم بحذر)، --local (مقارنة مع schema.js المحلي الخاص بك بدلاً من المنشور). بدون علم، migrate يتطلب طرفًا ويحدث خطأ في بيئة غير تفاعلية بدلاً من التخمين.

run

npx tgcloud run <module> [args] [--ctx <json5>]

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

  • <module> — اسم عاري (يبحث تحت handlers/ و endpoints/) أو مسار مثل handlers/message. اسم عاري موجود في كلا الدليلين يجب أن يُكتب مع دليله.
  • [args] — الحمولة التي تم تمريرها إلى المعالج، مكتوبة بتنسيق JSON5 حتى تتمكن من تخطي اقتباس المفاتيح. إنه كائن نوع التحديث الذي يتلقاه المعالج (على سبيل المثال، رسالة لـ handlers/message)، أو input لنقطة النهاية، ويجب أن يكون كائنًا — وليس مصفوفة أو سلسلة نصية عارية.
  • --ctx <json5> — كائن السياق للوحدة (معامله الثاني)، أيضًا JSON5. استخدمه لتوفير ما يقرأه الكود الخاص بك من ctx — التحديث الخام للمعالج، --ctx '{ update: { update_id: 1 } }'، أو بيانات التهيئة لنقطة نهاية، --ctx '{ initData: { user: { id: 1 } } }'. run لا يتحقق منه، نظرًا لأنك تختبر منطقك، وليس فحوصات النظام الأساسي.
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'

يقوم النظام الأساسي بتشغيل الوحدة مقابل مساحة الوحدة المجمعة من مشروعك المحلي (لذا يتم استخدام كود lib/ المتغير محليًا أيضًا) ويعيد قيمة الإرجاع، وأي شيء تم تسجيله باستخدام console.*، والوقت المستغرق. اقرأ الوسيطات الكبيرة من ملف باستخدام npx tgcloud run handlers/message "$(cat message.json5)".

fetch

npx tgcloud fetch

يتحقق مما تم نشره حاليًا، دون لمس ملفات العمل الخاصة بك. بعد ذلك، تقارن status و diff بأحدث حالة للسحابة. مفيد لإلقاء نظرة على التعارض قبل اتخاذ قرار بشأن كيفية حله.

pull

npx tgcloud pull

يجعل مشروعك المحلي متوافقًا مع السحابة عن طريق تحديث ملفات العمل الخاصة بك إلى الحالة المنشورة.

pull لا يقوم أبدًا بكتابة مجلد بناء التطبيق المصغر الخاص بك.

reset

npx tgcloud reset

يتجاهل تغييراتك المحلية ويعيد الوحدات من آخر حالة معروفة للسحابة. استخدمه للتخلص من تجربة. مجلد بناء التطبيق المصغر لا يتم لمسه.

webhook

npx tgcloud webhook
npx tgcloud webhook sync [--drop-pending]

يقوم تيليجرام بتوصيل التحديثات إلى روبوتك عبر خطاف ويب، والذي تديره المنصة لك — لا تقوم أبدًا بتوجيهه إلى أي مكان يدويًا. npx tgcloud webhook يعرض حالته الحالية: عنوان URL، قائمة allowed_updates، عدد التحديثات المعلقة، آخر خطأ تسليم (إن وجد)، وما إذا كان متزامنًا مع المعالجات المنشورة الخاصة بك.

“متزامن” يعني أن خطاف الويب يشير إلى المنصة وأن allowed_updates الخاص به يتطابق مع المعالجات التي نشرتها — لذا يقوم تيليجرام بتوصيل أنواع التحديثات التي تعالجها بالضبط، ولا شيء آخر. قد يؤدي نشر معالج جديد (أو إزالة واحد) إلى ترك خطاف الويب غير متزامن حتى يتم تحديثه؛ npx tgcloud status يشير إلى هذا أيضًا.

npx tgcloud webhook sync يصلح ذلك — يعيد توجيه خطاف الويب إلى المنصة ويعيد بناء allowed_updates من المعالجات المنشورة الخاصة بك. أضف --drop-pending لتجاهل التحديثات التي كان تيليجرام قد وضعها بالفعل في قائمة الانتظار قبل المزامنة (وإلا فسيتم تسليمها بمجرد أن يصبح خطاف الويب صحيًا مرة أخرى).

completion

ملاحظة: الإكمال التلقائي باستخدام المفتاح <Tab> يعمل فقط عندما يكون tgcloud عاريًا موجودًا على PATH الخاص بك — لذا قم بتثبيته عالميًا (npm install -g @tgcloud/cli) أو بخلاف ذلك ضع الثنائي على PATH الخاص بك. لا يمكنه ربط npx.

tgcloud completion <bash|zsh|fish>

يطبع نصًا برمجيًا للإكمال التلقائي إلى stdout. قم بتمكينه مرة واحدة، ثم <Tab> يكمل الأوامر، والأعلام، ودلائل الوحدات، وأنواع تحديثات المعالجات التي لم تحصل عليها بعد، والوحدات القابلة للتشغيل المحلية الخاصة بك.

# bash — يحتاج إلى حزمة bash-completion:
echo 'eval "$(tgcloud completion bash)"' >> ~/.bashrc

# zsh — تأكد من تشغيل `autoload -U compinit && compinit` في ~/.zshrc الخاص بك:
echo 'eval "$(tgcloud completion zsh)"' >> ~/.zshrc

# fish:
tgcloud completion fish > ~/.config/fish/completions/tgcloud.fish

أعد تشغيل الصدفة (أو أعد تحميل الملف) بعد ذلك. تشغيل tgcloud completion بدون صدفة يطبع هذه التعليمات مرة أخرى.

upgrade

npx tgcloud upgrade [--dry-run]

ترحيل لمرة واحدة للمشاريع التي تم إنشاؤها قبل CLI 0.2.0، والتي احتفظت بـ schema.js، lib/ و handlers/ في جذر المشروع. يقوم بنقلها إلى tgcloud/. يتم الاحتفاظ برابطك بالروبوت، ولا يلزم إعادة نشر أي شيء. --dry-run يطبع التحركات المخطط لها دون لمس أي شيء. الأوامر الأخرى لا تعمل على التخطيط القديم حتى تقوم بالترقية.

البقاء متزامنًا

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

npx tgcloud fetch           # انظر ما تغير في السحابة، ثم أعد التحقق
npx tgcloud pull            # جلب الأحدث إلى ملفات العمل الخاصة بك
npx tgcloud push --force    # الكتابة فوق حالة السحابة (خطير)

هذا الفحص المتزامن المتفائل هو السبب في أنه يمكنك مشاركة روبوت عبر فريق دون عملية نشر متزامنة. تخرج الأوامر بقيمة غير صفرية عند الفشل — رفض نشر، فشل ترحيل، خطأ مصادقة، وحدة أدت إلى خطأ أثناء run — لذا فهي تتألف بشكل نظيف في نصوص برمجية وخطوط أنابيب CI.