
به اشتراک بگذارید

به اشتراک بگذارید
توکن را از بازوی پدر گرفتهاید و ربات روی لپتاپ به /start جواب میدهد؛ ساخت ربات بله روی میز شما تمام شده است. حالا همین کد باید به سروری برود که خواب نمیرود، با توکنی بیرون از کد و راه برگشتی برای روزی که نسخه تازه خراب از آب درآمد.
💡 بله به ربات میگوید «بازو»
مستندات رسمی بله ربات را «بازو» مینامد و درخواستها را به https://tapi.bale.ai/bot<token>/METHOD_NAME میفرستد.
✅ آنچه ربات باید بعد از خاموشی به یاد بیاورد، از حافظه پایتون بیرون میرود؛ کدها فقط متدهای فهرست رسمی بله را صدا میزنند.
کد کار میکند؛ آنچه از اینجا عوض میشود محل اجرای ربات است و رفتارش هنگام خطا. معلم خصوصی در خانه برنامه کلاسها را در ذهن داشت و روز بیماری کلاس را تعطیل میکرد؛ به آموزشگاه که برود، دفتر حضور و غیاب و معلم جانشین منتظرش است. سهم ربات از این جابهجایی دیتابیس و ریاستارت خودکار است؛ سرور و وبهوک را راهنمای میزبانی ربات بله بر عهده دارد.
📌 هر تکه ربات کجا مینشیند
◀️ توکن و نمونه اولیه روی لپتاپ
◀️ کتابخانه، دستورها و دکمهها
◀️ سرور، systemd و برگشت
◀️ دیتابیس کاربران

برای ساخت ربات بله دو چیز لازم است: توکنی که @botfather در بله میدهد و اسکریپتی که با getUpdates پیام میخواند و با sendMessage جواب میدهد. مستندات رسمی بله شکل توکن را 123456:ABC-DEF... نشان میدهد.
با @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 روی فایلهای ردیابیشده اثری ندارد. توکن را عوض کنید؛ روش باطل کردنش در مستندات بله نیامده و باید در منوی بازوی پدر بگردید.

ابزار اصلی ساخت ربات بله در این راهنما requests است: هر متد بله یک درخواست HTTP است و فقط متدهای مستند صدا زده میشوند. کتابخانههای بزرگتر درخواستهایی هم میفرستند که باید روی بله امتحان شوند.
| کتابخانه | نسخه در PyPI (۱۳ آبان ۱۴۰۵) | پایتون لازم | نکته برای بله |
|---|---|---|---|
| کتابخانه requests | 2.34.2 | 3.10 به بالا | مستندات requests: تقریباً همه کدهای production باید timeout بدهند. |
| کتابخانه python-bale-bot | 2.5.0 | 3.8 به بالا | مخصوص بله، نصب با pip install python-bale-bot -U؛ آخرین انتشارش بهمن ۱۴۰۲ است. |
| کتابخانه python-telegram-bot | 22.8 | 3.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 متنی است که کد با یک if تشخیصش میدهد. در ReplyKeyboardMarkup، فیلد keyboard ردیفهایی از KeyboardButton با فیلد text است و زدن دکمه همان متن را به شکل پیام معمولی میفرستد. پس هر دکمه یک جواب در دیکشنری REPLIES میخواهد؛ MENU، REPLIES و تابع handle در bot.py کامل بخش چهارم آمدهاند. متن sendMessage بین ۱ تا ۴۰۹۶ کاراکتر است.
دکمه شیشهای (InlineKeyboardButton) جز text فقط url یا callback_data میگیرد. سقف callback_data ۶۴ بایت است و هر حرف فارسی در UTF-8 دو بایت میخورد؛ کد کوتاه انگلیسی مثل hours بگذارید. کلیک، آپدیتی با callback_query میفرستد؛ حلقه بخش چهارم فقط message را جواب میدهد، پس برایش شاخهای با sendMessage اضافه کنید. ساختار InlineKeyboardMarkup در مستندات بله تعریف نشده و باید روی بازوی آزمایشی امتحانش کرد.
روی سرور، کد در پوشهای با تاریخ انتشار مینشیند، توکن زیر /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 میگذارد.
هر انتشار پوشه و 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 پشت سر هم، هر پنج ثانیه یک بار.

فهرست کاربران و آخرین 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 لازم شد یا ربات روبیکا کنار همین ربات آمد، راهنمای زیر هر دو را پوشش میدهد.
ربات شما الان روی لپتاپ است یا سرور، و فهرست کاربرانش در حافظه میماند یا در فایل؟
منابع