بازگشت به وبلاگ
کتابخانه ربات بله و تلگرام؛ ساخت ربات با TeleBot و مثال‌های کاربردی

کتابخانه ربات بله و تلگرام؛ ساخت ربات با TeleBot و مثال‌های کاربردی

۱۴۰۵/۵/۳۰admin

اگر بخواهیم برای تلگرام یا پیام‌رسان بله یک ربات بسازیم، یکی از ساده‌ترین روش‌ها استفاده از کتابخانه‌های مبتنی بر مفهوم TeleBot است. این کتابخانه‌ها بخش زیادی از جزئیات ارتباط مستقیم با API را از برنامه‌نویس پنهان می‌کنند و اجازه می‌دهند به‌جای درگیر شدن با درخواست‌های HTTP، روی منطق ربات تمرکز کنیم.

در این مقاله، ابتدا مفهوم TeleBot را بررسی می‌کنیم، سپس یک ربات ساده می‌سازیم و در ادامه سراغ قابلیت‌هایی مانند دریافت پیام، دستورها، دکمه‌ها، فایل‌ها، مدیریت کاربران و ساختار مناسب برای پروژه‌های بزرگ‌تر می‌رویم.

نکته: API تلگرام و API بله از نظر مفاهیم بسیار به یکدیگر نزدیک هستند، اما جزئیات endpointها، توکن، قابلیت‌های پشتیبانی‌شده و نسخه کتابخانه می‌تواند متفاوت باشد. بنابراین در پروژه واقعی باید مستندات نسخه‌ای که استفاده می‌کنید نیز بررسی شود.


TeleBot چیست؟

TeleBot معمولاً به یک رابط سطح‌بالا برای ساخت ربات‌های تلگرامی اشاره دارد. در اکوسیستم Python، یکی از معروف‌ترین پیاده‌سازی‌ها pyTelegramBotAPI است که امکان استفاده از کلاسی با نام TeleBot را فراهم می‌کند.

به‌جای اینکه خودمان درخواست‌هایی شبیه زیر ایجاد کنیم:

POST https://api.telegram.org/bot<TOKEN>/sendMessage

و JSON مربوط به درخواست را مدیریت کنیم، می‌توانیم از یک API پایتونی ساده‌تر استفاده کنیم:

bot.send_message(chat_id, "سلام!")

در نتیجه، TeleBot را می‌توان یک لایه انتزاعی (Abstraction Layer) روی API ربات پیام‌رسان در نظر گرفت.


چرا از TeleBot استفاده کنیم؟

فرض کنید رباتی داریم که باید:

  1. پیام کاربر را دریافت کند.
  2. متن پیام را بررسی کند.
  3. در صورت ارسال /start، پیام خوش‌آمدگویی بفرستد.
  4. در صورت ارسال یک فایل، آن را پردازش کند.
  5. برای کاربر دکمه نمایش دهد.
  6. نتیجه را دوباره برای کاربر ارسال کند.

بدون یک کتابخانه مناسب، باید بخش قابل توجهی از کد مربوط به HTTP، JSON، polling، خطاها و مدیریت updateها را خودمان پیاده‌سازی کنیم.

TeleBot این قسمت‌ها را ساده‌تر می‌کند:

from telebot import TeleBot

bot = TeleBot("BOT_TOKEN")

@bot.message_handler(commands=["start"])
def start(message):
    bot.reply_to(message, "سلام! به ربات خوش آمدید.")

bot.infinity_polling()

همین چند خط یک ربات ساده را ایجاد می‌کند.


نصب کتابخانه

برای ربات تلگرام، اگر از pyTelegramBotAPI استفاده می‌کنید، نصب معمولاً به شکل زیر است:

pip install pyTelegramBotAPI

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

import telebot

bot = telebot.TeleBot("BOT_TOKEN")

یا:

from telebot import TeleBot

bot = TeleBot("BOT_TOKEN")

ساخت اولین ربات تلگرام

برای ساخت ربات تلگرام ابتدا باید یک Bot Token داشته باشیم.

این توکن را باید مانند یک رمز عبور در نظر بگیریم.

هرگز توکن ربات را داخل Git، README عمومی یا کد منتشرشده قرار ندهید.

بهتر است از متغیر محیطی استفاده کنیم:

import os
import telebot

TOKEN = os.environ["BOT_TOKEN"]

bot = telebot.TeleBot(TOKEN)

برای مثال در Linux می‌توانیم متغیر را تعریف کنیم:

export BOT_TOKEN="123456:ABC..."

و سپس برنامه را اجرا کنیم.


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

کد زیر به پیام /start پاسخ می‌دهد:

import os
import telebot

bot = telebot.TeleBot(os.environ["BOT_TOKEN"])


@bot.message_handler(commands=["start"])
def start(message):
    bot.reply_to(
        message,
        "سلام 👋\nبه ربات ما خوش آمدید."
    )


bot.infinity_polling()

در این مثال چند مفهوم مهم داریم.

message_handler

این decorator مشخص می‌کند که یک تابع باید برای چه نوع پیام‌هایی اجرا شود.

مثلاً:

@bot.message_handler(commands=["start"])

یعنی هر زمان کاربر دستور /start را ارسال کرد، تابع زیر اجرا شود.


دریافت تمام پیام‌های متنی

اگر بخواهیم هر پیام متنی را دریافت کنیم، می‌توانیم بنویسیم:

@bot.message_handler(content_types=["text"])
def handle_text(message):
    print(message.text)

    bot.reply_to(
        message,
        f"شما نوشتید: {message.text}"
    )

برای مثال اگر کاربر بنویسد:

سلام

ربات پاسخ می‌دهد:

شما نوشتید: سلام

استفاده از message

پارامتر message اطلاعات زیادی درباره پیام در اختیار ما قرار می‌دهد.

برای مثال:

@bot.message_handler(content_types=["text"])
def handle_text(message):
    print(message.text)
    print(message.chat.id)
    print(message.from_user.id)
    print(message.from_user.first_name)

اطلاعات مهم عبارت‌اند از:

ویژگیکاربرد
message.textمتن پیام
message.chat.idشناسه چت
message.from_user.idشناسه کاربر
message.from_user.first_nameنام کاربر
message.chat.typeنوع چت
message.message_idشناسه پیام

پاسخ دادن به کاربر

دو روش متداول برای ارسال پاسخ وجود دارد.

روش اول:

bot.send_message(
    message.chat.id,
    "سلام!"
)

روش دوم:

bot.reply_to(
    message,
    "سلام!"
)

تفاوت مهم این است که reply_to پاسخ را به همان پیام کاربر مرتبط می‌کند.


ساخت دستورهای مختلف

فرض کنید ربات ما چند دستور دارد:

/start
/help
/about

می‌توانیم برای هرکدام handler جداگانه بنویسیم:

@bot.message_handler(commands=["start"])
def start(message):
    bot.reply_to(message, "ربات شروع به کار کرد.")


@bot.message_handler(commands=["help"])
def help_command(message):
    bot.reply_to(
        message,
        "دستورهای موجود:\n"
        "/start - شروع\n"
        "/help - راهنما\n"
        "/about - درباره ربات"
    )


@bot.message_handler(commands=["about"])
def about(message):
    bot.reply_to(
        message,
        "این ربات با Python ساخته شده است."
    )

این روش برای ربات‌های کوچک بسیار خوانا است.


استفاده از شرط برای پیام‌های متنی

می‌توانیم نوع پیام را نیز بررسی کنیم:

@bot.message_handler(content_types=["text"])
def handle_message(message):

    if message.text == "سلام":
        bot.reply_to(message, "سلام! 👋")

    elif message.text == "خداحافظ":
        bot.reply_to(message, "خداحافظ!")

    else:
        bot.reply_to(
            message,
            "متوجه پیام شما نشدم."
        )

اما در پروژه‌های بزرگ بهتر است منطق پردازش پیام را از handler جدا کنیم.

مثلاً:

def process_text(text):
    if text == "سلام":
        return "سلام! 👋"

    if text == "خداحافظ":
        return "خداحافظ!"

    return "پیام نامشخص."


@bot.message_handler(content_types=["text"])
def handle_message(message):
    response = process_text(message.text)
    bot.reply_to(message, response)

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


ساخت دکمه برای کاربر

یکی از قابلیت‌های بسیار مهم ربات‌ها، استفاده از keyboard است.

برای مثال:

from telebot import types

سپس:

keyboard = types.ReplyKeyboardMarkup(
    resize_keyboard=True
)

keyboard.add("درباره ما")
keyboard.add("تماس با ما")

و آن را برای کاربر ارسال می‌کنیم:

bot.send_message(
    message.chat.id,
    "یک گزینه را انتخاب کنید:",
    reply_markup=keyboard
)

کاربر به‌جای تایپ کردن متن، می‌تواند روی دکمه کلیک کند.


ساخت Inline Keyboard

نوع دیگری از دکمه‌ها، InlineKeyboardMarkup است.

مثلاً:

keyboard = types.InlineKeyboardMarkup()

button = types.InlineKeyboardButton(
    "وب‌سایت",
    url="https://example.com"
)

keyboard.add(button)

bot.send_message(
    message.chat.id,
    "برای ورود کلیک کنید:",
    reply_markup=keyboard
)

این نوع keyboard برای لینک‌ها، منوهای تعاملی و callbackها بسیار مفید است.


Callback Query چیست؟

فرض کنید می‌خواهیم کاربر با زدن یک دکمه، عملیات خاصی انجام دهد.

keyboard = types.InlineKeyboardMarkup()

button = types.InlineKeyboardButton(
    "نمایش اطلاعات",
    callback_data="show_info"
)

keyboard.add(button)

bot.send_message(
    message.chat.id,
    "یک گزینه را انتخاب کنید:",
    reply_markup=keyboard
)

اکنون باید callback را دریافت کنیم:

@bot.callback_query_handler(
    func=lambda call: call.data == "show_info"
)
def show_info(call):

    bot.answer_callback_query(call.id)

    bot.send_message(
        call.message.chat.id,
        "این اطلاعات توسط ربات تولید شده است."
    )

در اینجا:

callback_data="show_info"

مانند یک شناسه عمل می‌کند که به ما می‌گوید کاربر روی کدام دکمه کلیک کرده است.


یک مثال کامل‌تر: منوی ربات

می‌توانیم یک منوی ساده بسازیم:

import os
import telebot
from telebot import types

bot = telebot.TeleBot(os.environ["BOT_TOKEN"])


def main_menu():
    keyboard = types.InlineKeyboardMarkup()

    keyboard.add(
        types.InlineKeyboardButton(
            "📚 راهنما",
            callback_data="help"
        )
    )

    keyboard.add(
        types.InlineKeyboardButton(
            "ℹ️ درباره ما",
            callback_data="about"
        )
    )

    return keyboard


@bot.message_handler(commands=["start"])
def start(message):
    bot.send_message(
        message.chat.id,
        "به ربات خوش آمدید.",
        reply_markup=main_menu()
    )


@bot.callback_query_handler(func=lambda call: True)
def callback(call):

    if call.data == "help":
        text = "راهنمای ربات در این قسمت قرار می‌گیرد."

    elif call.data == "about":
        text = "این ربات با Python نوشته شده است."

    else:
        text = "گزینه نامعتبر است."

    bot.answer_callback_query(call.id)

    bot.send_message(
        call.message.chat.id,
        text
    )


bot.infinity_polling()

این ساختار پایه بسیاری از ربات‌های واقعی است.


دریافت فایل

ربات می‌تواند انواع مختلفی از محتوا را دریافت کند.

برای مثال:

@bot.message_handler(
    content_types=["document"]
)
def handle_document(message):

    file_info = bot.get_file(
        message.document.file_id
    )

    downloaded_file = bot.download_file(
        file_info.file_path
    )

    with open(
        "received_file",
        "wb"
    ) as file:
        file.write(downloaded_file)

    bot.reply_to(
        message,
        "فایل دریافت شد."
    )

فرآیند کلی به این صورت است:

User
  │
  │ ارسال فایل
  ▼
Telegram API
  │
  ▼
TeleBot
  │
  ├── get_file()
  │
  └── download_file()
  │
  ▼
Python Application

این الگو برای ربات‌هایی که فایل را پردازش می‌کنند بسیار کاربردی است.


پردازش فایل با Python

فرض کنید کاربر یک فایل CSV ارسال می‌کند و می‌خواهیم آن را با Python پردازش کنیم.

می‌توانیم پس از دریافت فایل، از pandas استفاده کنیم:

import pandas as pd
from io import BytesIO


@bot.message_handler(
    content_types=["document"]
)
def handle_csv(message):

    file_info = bot.get_file(
        message.document.file_id
    )

    data = bot.download_file(
        file_info.file_path
    )

    df = pd.read_csv(
        BytesIO(data)
    )

    rows = len(df)

    bot.reply_to(
        message,
        f"فایل دریافت شد.\nتعداد ردیف‌ها: {rows}"
    )

در نتیجه، ربات می‌تواند به یک رابط کاربری ساده برای یک سرویس پردازش داده تبدیل شود.


دریافت عکس

برای عکس نیز می‌توانیم handler جداگانه داشته باشیم:

@bot.message_handler(
    content_types=["photo"]
)
def handle_photo(message):

    photo = message.photo[-1]

    file_info = bot.get_file(
        photo.file_id
    )

    image = bot.download_file(
        file_info.file_path
    )

    with open(
        "image.jpg",
        "wb"
    ) as file:
        file.write(image)

    bot.reply_to(
        message,
        "تصویر دریافت شد."
    )

عبارت:

message.photo[-1]

معمولاً آخرین و در نتیجه بزرگ‌ترین نسخه تصویر موجود در پیام را انتخاب می‌کند.


ارسال عکس توسط ربات

ارسال فایل نیز ساده است:

with open("image.jpg", "rb") as photo:
    bot.send_photo(
        message.chat.id,
        photo
    )

برای فایل:

with open("report.pdf", "rb") as document:
    bot.send_document(
        message.chat.id,
        document
    )

مدیریت State در ربات

یکی از مسائل مهم در ربات‌های واقعی این است که بدانیم کاربر در چه مرحله‌ای قرار دارد.

فرض کنید ربات می‌گوید:

نام خود را وارد کنید:

و پس از آن:

شماره تلفن خود را وارد کنید:

و سپس:

شهر خود را وارد کنید:

در واقع ربات یک state machine ساده دارد:

START
  │
  ▼
WAITING_FOR_NAME
  │
  ▼
WAITING_FOR_PHONE
  │
  ▼
WAITING_FOR_CITY
  │
  ▼
COMPLETED

برای پروژه‌های جدی، بهتر است این stateها به شکل مشخص مدیریت شوند.

برای مثال:

user_states = {}

user_states[12345] = "WAITING_FOR_NAME"

سپس هنگام دریافت پیام:

user_id = message.from_user.id

state = user_states.get(user_id)

if state == "WAITING_FOR_NAME":
    ...

البته این روش ساده فقط برای نمونه است. در محیط production بهتر است stateها در یک storage مناسب مانند Redis یا database ذخیره شوند.


چرا نباید همه چیز را در یک فایل قرار دهیم؟

در ابتدای کار ممکن است تمام کد ما این‌گونه باشد:

bot.py

اما با بزرگ شدن پروژه، این ساختار مشکل‌ساز می‌شود.

برای یک پروژه متوسط می‌توانیم ساختاری مانند زیر داشته باشیم:

project/
│
├── bot.py
├── config.py
│
├── handlers/
│   ├── start.py
│   ├── messages.py
│   ├── callbacks.py
│   └── files.py
│
├── services/
│   ├── user_service.py
│   └── file_service.py
│
├── database/
│   ├── models.py
│   └── repository.py
│
└── requirements.txt

در این حالت:

  • handlers مسئول دریافت eventها هستند.
  • services منطق اصلی برنامه را انجام می‌دهند.
  • database مسئول ذخیره‌سازی است.
  • config.py تنظیمات را نگه می‌دارد.
  • bot.py برنامه را راه‌اندازی می‌کند.

این جداسازی در پروژه‌های بزرگ تفاوت بسیار زیادی ایجاد می‌کند.


TeleBot برای بله

اگر هدف ما ساخت ربات برای پیام‌رسان بله باشد، باید توجه کنیم که نمی‌توانیم صرفاً فرض کنیم تمام APIهای تلگرام دقیقاً در بله نیز وجود دارند.

معمولاً کتابخانه‌های بله که API آن را wrap می‌کنند، از همان ایده کلی استفاده می‌کنند:

bot = Bot(TOKEN)

و سپس:

bot.send_message(...)

یا handlerهایی مشابه:

@bot.message_handler(...)
def handler(message):
    ...

اما نام کلاس‌ها، package، endpointها و قابلیت‌های دقیق باید مطابق کتابخانه‌ای باشد که برای API بله انتخاب کرده‌ایم.

بنابراین اگر قرار است پروژه‌ای داشته باشیم که هم تلگرام و هم بله را پشتیبانی کند، پیشنهاد بهتر این است که منطق برنامه را از لایه پیام‌رسان جدا کنیم.


معماری مناسب برای پشتیبانی هم‌زمان از بله و تلگرام

فرض کنید ربات ما یک سرویس فروشگاهی است.

بهتر است منطق را این‌گونه طراحی کنیم:

                    ┌──────────────┐
                    │ Business     │
                    │ Logic        │
                    └──────┬───────┘
                           │
              ┌────────────┴────────────┐
              │                         │
       ┌──────▼──────┐           ┌──────▼──────┐
       │ Telegram    │           │ Bale        │
       │ Adapter     │           │ Adapter     │
       └──────┬──────┘           └──────┬──────┘
              │                         │
       Telegram API                Bale API

در این معماری، منطق اصلی برنامه نباید وابسته به Telegram باشد.

مثلاً بهتر است تابعی مانند:

def calculate_order_total(order):
    ...

هیچ اطلاعی از Telegram یا Bale نداشته باشد.

در مقابل، adapter مسئول تبدیل پیام‌رسان به مدل داخلی برنامه باشد.


مثال معماری چندلایه

فرض کنید کاربر می‌خواهد قیمت یک محصول را دریافت کند.

در لایه Telegram:

@bot.message_handler(commands=["price"])
def price(message):

    product = get_product("laptop")

    bot.reply_to(
        message,
        f"قیمت: {product.price}"
    )

اما بهتر است به شکل زیر عمل کنیم:

def get_product_price(product_id):
    product = product_repository.get(product_id)

    return product.price

و handler فقط وظیفه ارتباط با کاربر را داشته باشد:

@bot.message_handler(commands=["price"])
def price(message):

    price = get_product_price("laptop")

    bot.reply_to(
        message,
        f"قیمت محصول: {price}"
    )

این تفکیک باعث می‌شود منطق get_product_price را بتوانیم در ربات بله، API وب یا حتی یک برنامه دیگر نیز استفاده کنیم.


استفاده از Environment Variable

یکی از مهم‌ترین نکات امنیتی در ربات‌ها، نگهداری صحیح Token است.

روش بد:

TOKEN = "123456789:ABCDEF..."

روش بهتر:

import os

TOKEN = os.getenv("BOT_TOKEN")

و سپس:

export BOT_TOKEN="..."

همچنین فایل .env را نباید بدون بررسی وارد Git کنیم.

برای مثال:

.env

را در .gitignore قرار دهید.


مدیریت خطا

ربات‌های production ممکن است با خطاهای مختلفی مواجه شوند:

  • قطع شدن اینترنت
  • timeout
  • خطای API
  • فایل نامعتبر
  • database failure
  • خطای برنامه
  • درخواست‌های بیش از حد

بنابراین بهتر است خطاها را مدیریت کنیم.

مثلاً:

@bot.message_handler(commands=["calculate"])
def calculate(message):

    try:
        result = 100 / 0

        bot.reply_to(
            message,
            str(result)
        )

    except Exception:
        bot.reply_to(
            message,
            "در پردازش درخواست خطایی رخ داد."
        )

در پروژه واقعی بهتر است علاوه بر پاسخ مناسب به کاربر، خطا را نیز log کنیم:

import logging

logging.basicConfig(
    level=logging.INFO
)

logger = logging.getLogger(__name__)

و:

try:
    ...
except Exception:
    logger.exception(
        "Error while processing message"
    )

این کار برای پیدا کردن خطاهای production بسیار مهم است.


Polling یا Webhook؟

دو روش رایج برای دریافت updateهای ربات وجود دارد:

Polling
Webhook

Polling

در polling، برنامه مرتباً از API سؤال می‌کند:

آیا پیام جدیدی آمده؟
        ↓
      بله/خیر
        ↓
دوباره بررسی کن

برای شروع و پروژه‌های کوچک بسیار ساده است.

در TeleBot معمولاً چیزی شبیه این می‌بینیم:

bot.infinity_polling()

Webhook

در webhook، پیام‌رسان درخواست HTTP را به سرور ما ارسال می‌کند:

User
  │
  ▼
Telegram/Bale
  │
  │ HTTP POST
  ▼
Our Server
  │
  ▼
Bot Application

Webhook معمولاً برای deploymentهای production که سرور HTTP در اختیار داریم، معماری مناسبی است.


چه زمانی Polling مناسب‌تر است؟

برای این موارد polling معمولاً انتخاب ساده‌تری است:

  • یادگیری
  • نمونه اولیه
  • ربات شخصی
  • پروژه کوچک
  • محیط development

مثلاً:

bot.infinity_polling()

برای شروع کاملاً مناسب است.

اما در سیستم‌های بزرگ‌تر باید موضوعاتی مانند:

  • scaling
  • load balancing
  • concurrency
  • reliability
  • graceful shutdown

را نیز در نظر گرفت.


ربات را مانند یک Web Application طراحی کنید

یک اشتباه رایج این است که ربات را صرفاً یک فایل Python ببینیم.

در واقع ربات می‌تواند یک application کامل باشد:

                ┌──────────────┐
                │ Telegram/Bale│
                └───────┬──────┘
                        │
                        ▼
                ┌──────────────┐
                │ Bot Handlers │
                └───────┬──────┘
                        │
                        ▼
                ┌──────────────┐
                │ Application  │
                │ Services     │
                └───────┬──────┘
                        │
              ┌─────────┴─────────┐
              ▼                   ▼
        ┌──────────┐        ┌──────────┐
        │ Database │        │ External │
        │          │        │ APIs     │
        └──────────┘        └──────────┘

این نگاه باعث می‌شود طراحی ربات از ابتدا قابل توسعه باشد.


یک مثال کاربردی: ربات دریافت اطلاعات کاربر

فرض کنید می‌خواهیم یک فرم ساده ایجاد کنیم.

کاربر /start را می‌فرستد:

@bot.message_handler(commands=["start"])
def start(message):

    bot.send_message(
        message.chat.id,
        "لطفاً نام خود را وارد کنید."
    )

سپس باید state کاربر را ذخیره کنیم:

user_state = {}

user_state[message.from_user.id] = {
    "step": "name"
}

در پیام بعد:

@bot.message_handler(content_types=["text"])
def handle_text(message):

    user_id = message.from_user.id

    state = user_state.get(user_id)

    if not state:
        bot.reply_to(
            message,
            "لطفاً ابتدا /start را ارسال کنید."
        )
        return

    if state["step"] == "name":

        state["name"] = message.text
        state["step"] = "city"

        bot.reply_to(
            message,
            "نام ثبت شد. حالا شهر خود را وارد کنید."
        )

    elif state["step"] == "city":

        state["city"] = message.text

        bot.reply_to(
            message,
            "اطلاعات شما با موفقیت دریافت شد."
        )

        del user_state[user_id]

این نمونه ساده است، اما ایده اصلی conversation flow را نشان می‌دهد.


نکته مهم درباره State

در مثال بالا از یک dictionary استفاده کردیم:

user_state = {}

این روش فقط برای مثال آموزشی مناسب است.

اگر برنامه restart شود، تمام stateها از بین می‌روند.

در production بهتر است state در یکی از این موارد نگهداری شود:

PostgreSQL
Redis
SQLite
MongoDB

انتخاب دقیق به معماری پروژه بستگی دارد.


تست کردن ربات

کد ربات را نباید فقط با ارسال دستی پیام تست کنیم.

برای منطق business بهتر است unit test داشته باشیم.

مثلاً:

def calculate_discount(price, percent):
    return price * (1 - percent / 100)

می‌توانیم آن را مستقل از Telegram تست کنیم:

def test_calculate_discount():
    assert calculate_discount(
        1000,
        20
    ) == 800

این یکی از دلایل مهم جداسازی business logic از bot handler است.


ساختار پیشنهادی برای پروژه

برای یک پروژه نسبتاً جدی می‌توانیم از ساختار زیر استفاده کنیم:

telegram_bot/
│
├── app/
│   ├── handlers/
│   │   ├── start.py
│   │   ├── user.py
│   │   ├── admin.py
│   │   └── callbacks.py
│   │
│   ├── services/
│   │   ├── users.py
│   │   ├── orders.py
│   │   └── payments.py
│   │
│   ├── repositories/
│   │   ├── users.py
│   │   └── orders.py
│   │
│   ├── models/
│   │   └── user.py
│   │
│   └── config.py
│
├── tests/
│
├── bot.py
├── requirements.txt
└── .env

این ساختار الزام نیست، اما برای پروژه‌ای که قرار است رشد کند، بسیار بهتر از یک فایل چند هزار خطی است.


اشتباهات رایج در ساخت ربات

۱. قرار دادن Token داخل Git

اشتباه:

TOKEN = "REAL_TOKEN"

بهتر:

TOKEN = os.environ["BOT_TOKEN"]

۲. قرار دادن تمام منطق در Handler

بد:

@bot.message_handler(...)
def handler(message):

    # database
    # business logic
    # API request
    # calculations
    # formatting
    # response

بهتر:

@bot.message_handler(...)
def handler(message):

    result = service.process(
        message.text
    )

    bot.reply_to(
        message,
        result
    )

۳. استفاده از Database داخل هر handler

اگر تمام handlerها مستقیماً SQL اجرا کنند، نگهداری پروژه سخت خواهد شد.

بهتر است از repository یا service layer استفاده کنیم.


۴. نادیده گرفتن Rate Limit

در ربات‌های پرترافیک، تعداد درخواست‌های API اهمیت زیادی دارد.

نباید تصور کنیم که می‌توانیم بدون محدودیت:

for user in users:
    bot.send_message(...)

را اجرا کنیم.

برای سیستم‌های بزرگ باید rate limiting، queue و retry strategy را در نظر گرفت.


آیا TeleBot برای همه پروژه‌ها مناسب است؟

خیر.

TeleBot برای بسیاری از پروژه‌های کوچک و متوسط بسیار راحت است، اما تنها انتخاب موجود نیست.

در Python کتابخانه‌ها و frameworkهای دیگری نیز وجود دارند که معماری‌های متفاوتی ارائه می‌کنند.

برای مثال، ممکن است یک پروژه به frameworkی با معماری asynchronous نیاز داشته باشد، مخصوصاً زمانی که تعداد درخواست‌ها زیاد است یا برنامه هم‌زمان با سرویس‌های خارجی زیادی ارتباط برقرار می‌کند.

بنابراین انتخاب کتابخانه باید بر اساس نیاز پروژه انجام شود، نه صرفاً محبوبیت آن.


چه زمانی TeleBot انتخاب خوبی است؟

TeleBot انتخاب مناسبی است اگر:

  • می‌خواهید سریع یک bot بسازید.
  • با Python راحت هستید.
  • API ساده و قابل فهم می‌خواهید.
  • پروژه کوچک یا متوسط است.
  • می‌خواهید با handlerها کار کنید.
  • می‌خواهید سریع prototype بسازید.

برای یادگیری bot development نیز مدل handler-based آن بسیار قابل فهم است.


چه زمانی باید معماری پیچیده‌تری انتخاب کنیم؟

اگر ربات شما دارای:

  • هزاران یا میلیون‌ها کاربر
  • پردازش سنگین
  • درخواست‌های هم‌زمان زیاد
  • چندین سرویس خارجی
  • سیستم پرداخت
  • صف پردازش
  • database بزرگ
  • چند instance از bot

باشد، دیگر صرفاً داشتن چند message_handler کافی نیست.

در چنین پروژه‌ای باید به معماری کامل application فکر کنیم.

مثلاً:

                    Load Balancer
                         │
             ┌───────────┼───────────┐
             ▼           ▼           ▼
           Bot 1       Bot 2       Bot 3
             │           │           │
             └───────────┼───────────┘
                         ▼
                       Redis
                         │
                         ▼
                    Message Queue
                         │
             ┌───────────┴───────────┐
             ▼                       ▼
         Worker 1                 Worker 2
             │                       │
             └───────────┬───────────┘
                         ▼
                     PostgreSQL

این معماری البته برای همه ربات‌ها ضروری نیست؛ نباید یک ربات ساده را بدون دلیل به یک سیستم توزیع‌شده تبدیل کرد.


جمع‌بندی

TeleBot یک راه ساده و قابل فهم برای ساخت ربات‌های Python-based است. ایده اصلی آن ساده است:

Message
   ↓
Handler
   ↓
Business Logic
   ↓
Response

برای یک ربات ساده می‌توانیم با چند خط کد شروع کنیم:

import os
import telebot

bot = telebot.TeleBot(
    os.environ["BOT_TOKEN"]
)


@bot.message_handler(
    commands=["start"]
)
def start(message):

    bot.reply_to(
        message,
        "سلام! 👋"
    )


@bot.message_handler(
    content_types=["text"]
)
def text(message):

    bot.reply_to(
        message,
        f"شما گفتید: {message.text}"
    )


bot.infinity_polling()

اما نکته مهم این است که ساخت یک ربات واقعی فقط به دریافت و ارسال پیام محدود نمی‌شود. زمانی که پروژه بزرگ‌تر شود، باید به مدیریت state، database، logging، error handling، security، rate limiting، testing و معماری نرم‌افزار نیز توجه کنیم.

اگر قرار است ربات هم در تلگرام و هم در بله اجرا شود، بهترین رویکرد این است که منطق اصلی برنامه را از لایه ارتباط با پیام‌رسان جدا کنیم. در این حالت Telegram و Bale صرفاً دو adapter برای یک application مشترک خواهند بود.

در نهایت، برای پروژه‌های کوچک، شروع با TeleBot انتخاب بسیار خوبی است؛ اما با رشد پروژه بهتر است از همان ابتدا مرز مشخصی بین Bot API، handlerها و business logic ایجاد کنیم. همین تصمیم ساده، بعداً مهاجرت به معماری بزرگ‌تر یا اضافه کردن پیام‌رسان دیگری را بسیار آسان‌تر خواهد کرد.

نظرات

برای ارسال نظر باید وارد شوید.