توکن را از بازوی پدر گرفته‌اید و ربات روی لپ‌تاپ به /start جواب می‌دهد؛ ساخت ربات بله روی میز شما تمام شده است. حالا همین کد باید به سروری برود که خواب نمی‌رود، با توکنی بیرون از کد و راه برگشتی برای روزی که نسخه تازه خراب از آب درآمد.

  • ✅ نمونه اولیه با getUpdates
  • ✅ توکن بیرون از کد، در فایل محیطی
  • ✅ هر انتشار در پوشه‌ای با تاریخ
  • ✅ سرویس systemd با ری‌استارت خودکار
  • ✅ کاربران و offset در SQLite
  • ✅ برگشت با یک پیوند نمادین و یک ری‌استارت

💡 بله به ربات می‌گوید «بازو»

مستندات رسمی بله ربات را «بازو» می‌نامد و درخواست‌ها را به https://tapi.bale.ai/bot<token>/METHOD_NAME می‌فرستد.
✅ آنچه ربات باید بعد از خاموشی به یاد بیاورد، از حافظه پایتون بیرون می‌رود؛ کدها فقط متدهای فهرست رسمی بله را صدا می‌زنند.

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

ساخت ربات بله، درخت پوشه‌های نسخه ربات پایتونی با پیوند current

نقطه شروع؛ توکن از BotFather بله و ربات روی سیستم خودتان

برای ساخت ربات بله دو چیز لازم است: توکنی که @botfather در بله می‌دهد و اسکریپتی که با getUpdates پیام می‌خواند و با sendMessage جواب می‌دهد. مستندات رسمی بله شکل توکن را 123456:ABC-DEF... نشان می‌دهد.

گرفتن توکن و امتحانش با getMe

با @botfather گفت‌وگو کنید و از منوی خودش بازو بسازید؛ مستندات نام دستورهایش را نیاورده. توکن رمز ربات است و getMe ساده‌ترین امتحانش. پاسخ JSON فیلد ok دارد و اگر false باشد، علت در description می‌آید.

curl "https://tapi.bale.ai/bot123456:YOUR-TOKEN-HERE/getMe"

نمونه اولیه، همان نسخه لپ‌تاپی

نسخه لپ‌تاپی کم‌وبیش همین است: یک حلقه و توکنی که وسط کد نشسته.

import time, requests

TOKEN = "123456:YOUR-TOKEN-HERE"   # prototype only
API = f"https://tapi.bale.ai/bot{TOKEN}"
offset = 0
while True:
    r = requests.post(f"{API}/getUpdates", json={"offset": offset}, timeout=30)
    for upd in r.json()["result"]:
        offset = upd["update_id"] + 1
        msg = upd.get("message")
        if msg and msg.get("text") == "/start":
            requests.post(f"{API}/sendMessage", timeout=30,
                          json={"chat_id": msg["chat"]["id"], "text": "ربات روشن است."})
    time.sleep(1)

طبق مستندات بله، درخواستی با offset بزرگ‌تر آپدیت‌های قبلی را تأیید می‌کند. limit بین ۱ تا ۱۰۰ است و پارامتر timeout در فهرست getUpdates نیامده، برای همین حلقه یک ثانیه صبر می‌کند. بله فعلاً ۲۰۰۰ پیام آخر را ۲۴ ساعت نگه می‌دارد.

⚠️ توکنی که یک بار commit شد، دیگر رمز نیست

اگر خراب شد و فایل توکن‌دار به git رفت، .gitignore کمکی نمی‌کند؛ طبق مستندات git روی فایل‌های ردیابی‌شده اثری ندارد. توکن را عوض کنید؛ روش باطل کردنش در مستندات بله نیامده و باید در منوی بازوی پدر بگردید.

نقطه شروع؛ توکن از BotFather بله و ربات روی سیستم خودتان

انتخاب کتابخانه پایتون و ساخت دستورها و دکمه‌ها

ابزار اصلی ساخت ربات بله در این راهنما requests است: هر متد بله یک درخواست HTTP است و فقط متدهای مستند صدا زده می‌شوند. کتابخانه‌های بزرگ‌تر درخواست‌هایی هم می‌فرستند که باید روی بله امتحان شوند.

کتابخانهنسخه در PyPI (۱۳ آبان ۱۴۰۵)پایتون لازمنکته برای بله
کتابخانه requests2.34.23.10 به بالامستندات requests: تقریباً همه کدهای production باید timeout بدهند.
کتابخانه python-bale-bot2.5.03.8 به بالامخصوص بله، نصب با pip install python-bale-bot -U؛ آخرین انتشارش بهمن ۱۴۰۲ است.
کتابخانه python-telegram-bot22.83.10 به بالابرای تلگرام؛ run_polling پارامتر timeout ده‌ثانیه‌ای می‌فرستد که در فهرست بله نیست.

🔸 نشانی پایه در کتابخانه‌های تلگرامی

در python-telegram-bot، ApplicationBuilder.base_url و base_file_url برای بله می‌شوند https://tapi.bale.ai/bot و https://tapi.bale.ai/file/bot. این فقط مقصد است؛ answerCallbackQuery و deleteWebhook در فهرست بله نیستند و مستندات بله ادعای سازگاری با API تلگرام نکرده است.

دستور /start و منوی دکمه‌ای

برای ربات، /start متنی است که کد با یک if تشخیصش می‌دهد. در ReplyKeyboardMarkup، فیلد keyboard ردیف‌هایی از KeyboardButton با فیلد text است و زدن دکمه همان متن را به شکل پیام معمولی می‌فرستد. پس هر دکمه یک جواب در دیکشنری REPLIES می‌خواهد؛ MENU، REPLIES و تابع handle در bot.py کامل بخش چهارم آمده‌اند. متن sendMessage بین ۱ تا ۴۰۹۶ کاراکتر است.

دکمه شیشه‌ای و callback_data

دکمه شیشه‌ای (InlineKeyboardButton) جز text فقط url یا callback_data می‌گیرد. سقف callback_data ۶۴ بایت است و هر حرف فارسی در UTF-8 دو بایت می‌خورد؛ کد کوتاه انگلیسی مثل hours بگذارید. کلیک، آپدیتی با callback_query می‌فرستد؛ حلقه بخش چهارم فقط message را جواب می‌دهد، پس برایش شاخه‌ای با sendMessage اضافه کنید. ساختار InlineKeyboardMarkup در مستندات بله تعریف نشده و باید روی بازوی آزمایشی امتحانش کرد.

انتقال به سرور با systemd و راه برگشت به نسخه قبل

روی سرور، کد در پوشه‌ای با تاریخ انتشار می‌نشیند، توکن زیر /etc و داده زیر /var/lib. systemd ربات را بعد از ریبوت و خطا بالا می‌آورد و برگشت یک پیوند نمادین است. این دستورها لینوکسِ دارای systemd می‌خواهند؛ تصمیم بین لینوکس و ویندوز برای سرور مجازی را پیش از این بخش بگیرید.

توکن در فایل محیطی سرور

در bot.py توکن از os.environ['BALE_TOKEN'] خوانده می‌شود. فایل محیطی را از اول با دسترسی 600 بسازید تا حتی یک لحظه برای دیگران خواندنی نباشد:

sudo useradd --system --shell /usr/sbin/nologin balebot
sudo install -D -m 600 /dev/null /etc/balebot/bot.env
# then put two lines in it with sudo and an editor:
#   BALE_TOKEN=123456:YOUR-TOKEN-HERE
#   BOT_DB=/var/lib/balebot/bot.db

در مستندات systemd.exec آمده که بدون EnvironmentFile خواندنی، سرویس بالا نمی‌آید. همین صفحه متغیرهای محیطی را چون از راه D-Bus دیده می‌شوند برای رازها مناسب نمی‌داند؛ LoadCredential= سخت‌گیرتر است و توکن را فقط برای کاربر سرویس در $CREDENTIALS_DIRECTORY می‌گذارد.

پوشه نسخه‌ها و پیوند current

هر انتشار پوشه و venv خودش را دارد تا برگشت، کتابخانه‌ها را هم برگرداند. bot.py و requirements.txt (با یک خط requests==2.34.2) را با scp یا git به پوشه نسخه ببرید:

/opt/balebot/
├── releases/
│   ├── 2026-11-02/      bot.py  requirements.txt  venv/
│   └── 2026-11-04/      bot.py  requirements.txt  venv/
└── current -> /opt/balebot/releases/2026-11-04

/etc/balebot/bot.env     token, outside every release
/var/lib/balebot/bot.db  data, outside every release

داخل پوشه نسخه، چون /opt مال root است، sudo python3 -m venv venv و sudo venv/bin/pip install -r requirements.txt را اجرا کنید. یونیت فقط current را می‌شناسد:

# /etc/systemd/system/balebot.service
[Unit]
Description=Bale bot

[Service]
User=balebot
WorkingDirectory=/opt/balebot/current
EnvironmentFile=/etc/balebot/bot.env
ExecStart=/opt/balebot/current/venv/bin/python bot.py
StateDirectory=balebot
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

در مستندات systemd.service، Restart=on-failure بعد از خروج غیرصفر، کشته شدن با سیگنالی جز چهار سیگنال خروج عادی یا timeout سرویس را دوباره اجرا می‌کند، ولی بعد از stop دستی نه. پیش‌فرض RestartSec صد میلی‌ثانیه است؛ پنج ثانیه به شبکه فرصت می‌دهد. فعال‌سازی: sudo systemctl daemon-reload و sudo systemctl enable --now balebot.

🔁 انتشار و برگشت با یک پیوند

در ln -sfn، s پیوند نمادین می‌سازد، f مقصد موجود را جایگزین می‌کند و n نمی‌گذارد پیوند تازه داخل پوشه قبلی ساخته شود. انتشار: sudo ln -sfn /opt/balebot/releases/2026-11-04 /opt/balebot/current و بعد sudo systemctl restart balebot. برگشت: همان دو فرمان با پوشه 2026-11-02.

سخت‌ترین شب ساخت ربات بله شبی است که نسخه تازه بالا نمی‌آید؛ برگشت را پیش از اولین انتشار واقعی یک بار تمرین کنید.

اگر خراب شد: اول با پیوند current برگردید، بعد journalctl -u balebot -e را بخوانید؛ journal مقصد پیش‌فرض خروجی سرویس است. نبودن BALE_TOKEN یعنی KeyError پشت سر هم، هر پنج ثانیه یک بار.

انتقال به سرور با systemd و راه برگشت به نسخه قبل

داده کاربران را از حافظه به دیتابیس منتقل کنید

فهرست کاربران و آخرین update_id را در فایل SQLite بیرون از پوشه نسخه‌ها نگه دارید. sqlite3 در کتابخانه استاندارد پایتون است و StateDirectory=balebot پوشه /var/lib/balebot را برای کاربر سرویس می‌سازد و با توقف سرویس نگه می‌دارد.

حافظه را معمولاً آخر از همه در ساخت ربات بله جدی می‌گیرید: set کاربران با هر ری‌استارت خالی می‌شود و offset در حافظه جواب تکراری می‌سازد. این bot.py کامل است و جای نمونه اولیه را می‌گیرد:

import logging, os, sqlite3, time
import requests

API = f"https://tapi.bale.ai/bot{os.environ['BALE_TOKEN']}"
MENU = {"keyboard": [[{"text": "ساعت کاری"}, {"text": "پشتیبانی"}]]}
REPLIES = {"ساعت کاری": "شنبه تا چهارشنبه، ۹ تا ۱۷", "پشتیبانی": "سؤالتان را بفرستید."}

def call(method, **params):
    data = requests.post(f"{API}/{method}", json=params, timeout=30).json()
    if not data["ok"]:
        raise RuntimeError(f"{method}: {data.get('error_code')} {data.get('description')}")
    return data["result"]

def handle(msg):
    chat_id, text = msg["chat"]["id"], msg.get("text", "")
    if text == "/start":
        call("sendMessage", chat_id=chat_id, text="یک دکمه را بزنید.", reply_markup=MENU)
    elif text in REPLIES:
        call("sendMessage", chat_id=chat_id, text=REPLIES[text])

con = sqlite3.connect(os.environ["BOT_DB"])
with con:
    con.execute("CREATE TABLE IF NOT EXISTS users (chat_id INTEGER PRIMARY KEY)")
    con.execute("CREATE TABLE IF NOT EXISTS state (k TEXT PRIMARY KEY, v INTEGER)")
row = con.execute("SELECT v FROM state WHERE k = 'offset'").fetchone()
offset = row[0] if row else 0

while True:
    for upd in call("getUpdates", offset=offset, limit=100):
        msg = upd.get("message")
        try:
            if msg:
                handle(msg)
        except Exception:
            logging.exception("update %s failed", upd["update_id"])
        offset = upd["update_id"] + 1
        with con:
            if msg:
                con.execute("INSERT OR IGNORE INTO users (chat_id) VALUES (?)", (msg["chat"]["id"],))
            con.execute("INSERT OR REPLACE INTO state (k, v) VALUES ('offset', ?)", (offset,))
    time.sleep(1)

بلوک try دور handle عمدی است. کاربری که ربات را بسته، هر بار خطای sendMessage می‌گیرد. بدون try پروسه می‌افتد، systemd بالایش می‌آورد و همان آپدیت تأییدنشده دوباره می‌رسد؛ ربات برای همه قفل می‌شود. با try، logging.exception خطا را ثبت می‌کند و offset جلو می‌رود. فقط خطای بیرون از پردازش آپدیت، مثل قطع شبکه در getUpdates، سرویس را ری‌استارت می‌کند.

مستندات sqlite3 پایتون برای بستن راه تزریق SQL، جای‌نگهدار ? را به‌جای قالب‌بندی رشته توصیه می‌کند. بلوک with con: بدون خطا commit می‌کند و با خطا rollback.

اگر خراب شد: پیش از تغییر جدول، با sudo systemctl stop balebot سرویس را بایستانید و از bot.db کپی بگیرید. پیوند current دیتابیس را برنمی‌گرداند؛ فقط ستون با مقدار پیش‌فرض اضافه کنید و در INSERT نام ستون‌ها را بنویسید تا نسخه قبلی هم کار کند.

ربات روی لپ‌تاپ جواب می‌دهد و روی سرور ساکت است؟ پرسش سوم را اول بخوانید؛ بیشتر شب‌های اول انتقال همان‌جا تمام می‌شود.

توکن ربات بله را از کجا بگیرم؟

از @botfather در خود بله؛ با getMe امتحانش کنید.

کتابخانه python-telegram-bot با base_url به tapi.bale.ai وصل می‌شود؛ هر قابلیتش را روی بله امتحان کنید.

لاگ را با journalctl -u balebot -e. معمولاً فایل محیطی نیست، BALE_TOKEN خالی است یا پایتون سرور برای requests 2.34.2 قدیمی است.

چون offset در حافظه بوده. آخرین update_id را در SQLite ذخیره کنید.

بله فعلاً ۲۰۰۰ پیام آخر را ۲۴ ساعت نگه می‌دارد؛ بعد از آن ممکن است بخشی از دست برود.

آپدیتی با callback_query که data آن همان callback_data دکمه است، حداکثر ۶۴ بایت.

نه؛ فقط یک bot.env.example بدون توکن.

جمع‌بندی؛ ساخت ربات بله، این بار برای سرور یک توکن و یک حلقه getUpdates ربات را راه می‌اندازد؛ رباتی که کسی به آن تکیه کند، بیشتر می‌خواهد. توکن با دسترسی 600 در /etc/balebot/bot.env می‌نشیند و هر انتشار پوشه و venv خودش را دارد. systemd ربات را بعد از خطا و ریبوت بالا می‌آورد و برگشت یک ln -sfn است. خطای یک آپدیت ثبت و رد می‌شود و آخرین update_id در SQLite می‌ماند تا ری‌استارت جواب تکراری نفرستد.

🚀 سرور، شبکه و وب‌هوک ربات

اگر روزی وب‌هوک با گواهی TLS لازم شد یا ربات روبیکا کنار همین ربات آمد، راهنمای زیر هر دو را پوشش می‌دهد.

ربات شما الان روی لپ‌تاپ است یا سرور، و فهرست کاربرانش در حافظه می‌ماند یا در فایل؟

منابع

وب داده
وب داده

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

مقاله‌ها: 274
پاسخی بگذارید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *