alirezax5/telegram-base

Telegram Bot Base Framework (project skeleton).

Installs: 8

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Watchers: 0

Forks: 1

Open Issues: 0

Type:project

pkg:composer/alirezax5/telegram-base

v1.1.10 2025-11-13 08:33 UTC

This package is auto-updated.

Last update: 2025-11-13 08:34:28 UTC


README

توجه: این پروژه را ابتدا برای ربات‌های خودم توسعه دادم و تصمیم گرفتم آن را منتشر کنم تا اگر برای شما هم کاربردی بود، بتوانید از آن استفاده کنید.

در صورت مشکل در کار با پروژه از هوش مصنوعی جیمنای با لینک زیر استفاده کنید. اکثر موارد مورد نیاز به gem داده شده است.

https://gemini.google.com/gem/13NSCMFOipq6HMvSRPZ2NvhKGzuYVrTDS?usp=sharing

🎯 هدف پروژه

هدف از توسعه این بیس، سادگی در کدنویسی و بهینه‌سازی برای سرویس‌دهی به تعداد بالای کاربر است.
سعی کرده‌ام در حد امکان مصرف منابع (CPU / RAM / I/O) پایین باشد و ساختار پروژه قابل توسعه و انعطاف‌پذیر باقی بماند.

در بخش‌های مختلف پروژه از کمک هوش مصنوعی هم استفاده شده است. کمک گرفتن هیچ ایرادی ندارد — خروجی نهایی حاصل چند سال تجربه در ساخت ربات‌های تلگرامی است و با کمک AI روی بهینگی و انتخاب بهترین ساختار تمرکز شده است.

✅ امکانات بیس

  • پشتیبانی از انواع دیتابیس‌ها به واسطه illuminate/database
  • معماری پلاگین محور
  • پشتیبانی از چند زبانه کردن پروژه
  • قابلیت شخصی‌سازی در سطح مناسب
  • امکان انتخاب حالت اجرای ربات:
    • حالت معمولی
    • حالت صف‌بندی (Queue)
  • پشتیبانی از Queue با:
    • json
    • redis
    • rabbitmq
  • پشتیبانی از اجرای چند ورکر برای پردازش آپدیت‌ها (در حالت صف)
  • ساختار بهینه برای مدیریت دکمه‌ها و ترجمه‌ها
  • لاگ‌گیری حرفه‌ای با Monolog
  • کش مناسب برای جلوگیری از خواندن مجدد فایل‌ها و کاهش I/O
  • کلاس Shared برای اشتراک داده بین پلاگین‌ها
  • امکان تعیین اولویت و ترتیب اجرای پلاگین‌ها

❓ این بیس چه مشکلی را حل می‌کند؟

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

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

وقتی تعداد کاربران زیاد شود، درخواست‌های بیشتری به سمت سورس ارسال می‌شود و ممکن است برخی از آن‌ها میس (نادیده گرفته) شوند. اگر ربات شلوغی داشته باشید، احتمالاً از طرف BotFather پیامی دریافت کرده‌اید که سرعت ربات کاهش یافته است.

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

چرا صف‌بندی مهم است؟

  • می‌توانید چندین ورکر تعریف کنید
  • پردازش‌ها از درخواست‌ها جدا می‌شوند
  • مدیریت و کنترل لود بهتر انجام می‌شود

در صف‌بندی، آپدیت‌های تلگرام در یکی از موارد زیر ذخیره می‌شوند:

  • json
  • redis
  • rabbitmq

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

مزیت اصلی:

Load Balancing واقعی

می‌توانید:

  1. یک سرور فقط برای دریافت آپدیت‌ها از تلگرام داشته باشید
  2. چند سرور دیگر فقط برای پردازش آپدیت‌ها

نگران اجرای تکراری آپدیت ها نباشید — در بخش‌های مختلف سورس جلوی این موضوع گرفته شده است.

مزیت ها اگر از صف بندی و روش آپدیت وبهوک استفاده کنید :

  • فایل دریافت آپدیت تنها وظیفه ذخیره‌سازی دارد، نه اجرای کل سورس
  • زمان پاسخ‌دهی به وبهوک بسیار سریع‌تر می‌شود
  • داده‌ها در حافظه ذخیره می‌شوند و دوباره ساخته نمی‌شوند
  • کاهش محسوس مصرف منابع و افزایش راندمان

نصب

روش اول :

ابتدا برنامه composer رو روی سیستم خودتان یا سرور نصب کنید و در ترمینال دستور زیر رو اجرا کنید:

composer create-project alirezax5/telegram-base folderName

پس از نصب فایل .env رو ویرایش کنید همچنین دسترسی عمومی فایل رو ببندید.

روش دوم :

git clone https://github.com/alirezax5/telegramBase.git

نیازه که git روی سیستم نصب باشه.

پس از کلون دستور زیر رو در پوشه ی سورس بزنید :

composer install

روش سوم

دانلود از بخش releases

پس از دانلود دستور زیر رو در پوشه ی سورس بزنید :

composer install

فرقی نمیکنه از کدوم روش استفاده کنید.

حالت های ربات

به طور کلی ربات 2 حالت داره که در فایل .env با متغییر BOT_MODE نمایش داده می شود.

حالت ها :

  • update
  • webhook
  1. حالت update

در حالت Update Mode، با استفاده از متد getUpdates تلگرام، آپدیت‌ها دریافت و پردازش می‌شوند. در این حالت نیازی به ذخیره‌ی update_id نیست؛ خودِ بیس به‌طور خودکار آن را مدیریت می‌کند. فقط کافی است مشخص کنید برنامه چگونه باید اجرا و کنترل شود، زیرا این حالت در یک حلقه‌ی بی‌نهایت قرار ندارد.

برای این حالت، متغیر POLLING_LIMIT استفاده می‌شود که تعداد آپدیت‌هایی را که در هر بار فراخوانی دریافت می‌شوند، تعیین می‌کند.

  1. حالت webhook

در وبهوک دروقت درخواست از سمت تلگرام بیاد اجرا میشه.

حالت های آپدیت ربات

علاوه‌بر تعیین حالت ربات (Webhook یا Update Method)، یک حالت برای نحوه‌ی دریافت آپدیت‌ها نیز وجود دارد که در فایل .env با نام UPDATE_MODE تنظیم می‌شود. این مقدار می‌تواند یکی از دو حالت زیر باشد:

  1. normal همان‌طور که از نام این حالت مشخص است، همه‌چیز به‌صورت عادی عمل می‌کند و تغییری در رفتار معمول ربات نسبت به روش‌های استاندارد شما ایجاد نمی‌شود. اگر درخواست از طریق Webhook دریافت شود، همان لحظه اجرا می‌شود. و اگر حالت ربات روی Update Method تنظیم شده باشد، مطابق همان روش رفتار و پردازش انجام می‌گیرد.

  2. queue حالت بعدی، آپدیت صف‌بندی‌شده (Queue Update Mode) است. در این حالت، بر اساس مقدار تنظیم‌شده در BOT_MODE، آپدیت‌ها دریافت شده و مطابق نوع ذخیره‌سازی که توسط متغیر QUEUE_SAVE_TYPE مشخص می‌شود، ذخیره می‌شوند تا بعداً توسط Worker پردازش شوند.

در روش Update Method، لازم است حلقه‌ی بی‌نهایت را خودتان مدیریت کنید؛ چون در این حالت تمرکز این بیس روی مدیریت اجرای متد آپدیت نیست و تنها وظیفه‌ی آن ذخیره کردن آپدیت‌ها با استفاده از متد runFetchQueueUpdate است. ذخیره‌سازی نیز بر اساس مقدار QUEUE_SAVE_TYPE انجام می‌شود.

تنظیمات پیشنهادی برای حالت ها

اگر از هاستینگ استفاده می کنید و دسترسی به Supervisor ندارید :

BOT_MODE="webhook"
UPDATE_MODE="normal"

اگر در هاستینگ به redis و Supervisor دسترسی دارید :

BOT_MODE="webhook"
UPDATE_MODE="queue"
QUEUE_SAVE_TYPE="redis"

اگر سرور دارید rabbitmq و redis رو تست کنید و ببینید کدوم مناسب تره در سرور حتما از Supervisor استفاده کنید تا ورکر هارا مدیریت کند

BOT_MODE="webhook"
UPDATE_MODE="queue"
QUEUE_SAVE_TYPE="redis"

اگر در هاستینگ خود Supervisor در دسترس ندارید، مجبور خواهید شد از Cron Job استفاده کنید؛ در این حالت مدیریت تعداد پردازش‌ها بر عهده‌ی خود شماست. تلاش می‌کنم برای این دسته از کاربران یک قابلیت بهینه‌سازی فراهم کنم.

با این حال، پیشنهاد من این است که ربات را روی سرور (VPS) اجرا کنید و از Supervisor به همراه یکی از حالت‌های ذخیره‌سازی redis یا rabbit استفاده کنید. در ادامه، نحوه‌ی راه‌اندازی هر حالت را توضیح می‌دهم.

راه اندازی در سرور

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

برای نصب aaPanel به لینک زیر مراجعه کنید: https://www.aapanel.com/new/download.html

پس از نصب، از بخش App Store (App) ابزارهای مورد نیاز را نصب کنید.

سورس پروژه را در مسیر موردنظر آپلود کنید.

سپس از بخش Supervisor گزینه افزودن تسک (Add Daemon) را انتخاب کنید و فیلدها را به شکل زیر تکمیل کنید:

Name یک نام دلخواه به زبان انگلیسی وارد کنید (بدون فاصله).

Run User مقدار را روی root قرار دهید.

Process Directory مسیر پروژه را وارد کنید.

Start Command دستور اجرای ربات را وارد کنید.

چون از PHP استفاده می‌کنید، دستور مشابه یکی از موارد زیر خواهد بود:

php /yourpath/bot.php

یا نسخه php مورد نظر :

``` /www/server/php/84/bin/php /yourpath/bot.php ```

نکته: در مسیر بالا به جای 84 نسخه PHP نصب‌شده روی سرورتان را قرار دهید. و به جای مسیر، فایل مربوط به Worker را وارد کنید (در ادامه سورس دستور را قرار می‌دهم).

در ادامه تنظیمات تکمیلی Supervisor:

Processes تعداد Workerهای مورد نیاز. برای شروع مقدار 2 پیشنهاد می‌شود.

Startup Priority ترتیب اجرای برنامه‌ها؛ نیازی به تغییر ندارد.

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

اگر از cpanel و directadmin یا هر روش به جز aapanel میخواید استفاده کنید ابزار های مورد نیاز رو نصب کنید و اگه به کانفیگ پیشنهادی برای Supervisor نیاز داشتید :

[program:name]
command=/www/server/php/84/bin/php /www/wwwroot/domain/newBase/bot.php
directory=/www/wwwroot/domain/newBase
autorestart=true
startsecs=3
startretries=3
stdout_logfile=/www/server/panel/plugin/supervisor/log/test.out.log
stderr_logfile=/www/server/panel/plugin/supervisor/log/test.err.log
stdout_logfile_maxbytes=2MB
stderr_logfile_maxbytes=2MB
user=root
priority=999
numprocs=2
stopsignal=QUIT
process_name=name

فایل های مورد نیاز

اگر روش آپدیت normal استفاده می کنید :

use alirezax5\TelegramBase\App\Core;
include './vendor/autoload.php';
$core = new Core();
$core->run();

اگر از روش queue استفاده می کنید

فایل دریافت آپدیت ها :

use alirezax5\TelegramBase\App\Core;
include './vendor/autoload.php';
$core = new Core();
$core->run();

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

فایل ورکر ها :

<?php
use alirezax5\TelegramBase\App\Core;
include './vendor/autoload.php';
$core = new Core(true);
$core->runFetchQueueUpdate();
این فایل رو باید در Supervisor و یا cronjob ست کنید.

پیشنهاد میکنم از همون Supervisor استفاده کنید چون cronjob باید خودتون پروکسس هارو مدیریت کنید اما Supervisor خودش این کارا رو انجام منیده.

پلاگین ها

شما در فایل .env با مقدار PLUGINS_DIR میتونید مسیر پلاگین هارو مشخص کنید

پیشفرض در پوشه Plugin باید قرار بدید.

نمونه کد :

<?php

namespace alirezax5\TelegramBase\Plugin;

use alirezax5\TelegramBase\App\Plugin\PluginBase;
use alirezax5\TelegramBase\App\Shared\SharedManagement;
use telegramBotApiPhp\Telegram;

class Start implements PluginBase
{
    public function getPriority(): int
    {
        return 1;
    }

    public function onMessage($update, Telegram $telegram)
    {
        SharedManagement::set('findCommand', 'na');
        $telegram->sendMessage($update->from->id, $update->text, reply_markup: btn('start'));


    }
    public function onEditedMessage($update, Telegram $telegram)
    {

    }
    public function onCallbackQuery($update, Telegram $telegram)
    {

    }
    public function onInlineQuery($update, Telegram $telegram)
    {

    }
    public function onPollAnswer($update, Telegram $telegram)
    {

    }
  
    #.. سایر آپدیت ها
    
    public function before($update, Telegram $telegram)
    {

    }
    public function after($update, Telegram $telegram)
    {

    }
    
}

نام متدها در ساختار PascalCase تعریف می‌شوند و هر نوع آپدیت، به متد مخصوص خودش ارسال خواهد شد. پارامترهای $update و $telegram در تمام متدها به‌صورت ثابت ارسال می‌شوند.

متد getPriority

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

پیشنهاد می‌شود هنگام دریافت اطلاعات اولیه‌ی کاربر، مقدار اولویت را روی 0 قرار دهید. اگر ترتیب اجرای پلاگین برای شما اهمیتی ندارد، مقدار 999 را تنظیم کنید.

متدهای before و after

before قبل از اجرای متد مربوط به آپدیت فراخوانی می‌شود.

after پس از اجرای متد آپدیت اجرا خواهد شد.

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

زبان ها

بیس به شما این امکان رو میده یک ربات چندزبانه رو توسعه بدید در فایل .env شما می توانید زبان پیشفرض,مسیر زبان ها و مدت زمان کش زبان ها را تنظیم نمایید. پیشفرض در پوشه Language و با پسوند json باید متون خود رو ذخخیره کنید.

برای فرخوانی متن و تغییر زبان می توانید از مثال زیر استفاده کنید :

<?php

# دریافت ترجمه براساس نام کلید در فایل json
__('name');
# برای تنظیم زبان
Language::getInstance()->setLanguageDir( 'fa');
# برای جایگزین کردن مقدار خاص در متن ترجمه
__('name',['id'=>1]);
نمونه فایل زبان :
{
  "start": "متن شروع",
  "id": "نمونه متن با متغییر داینامیک #id",
  "btn_name": "نام"
}

دکمه ها

دکمه‌ها به‌صورت پیش‌فرض در فایل btn.php در ریشه‌ی سورس قرار دارند. در صورت نیاز، می‌توانید مسیر این فایل را از طریق تنظیمات .env تغییر دهید.

نمونه‌ی فایل تعریف دکمه‌ها:

<?php
return [
    "remove" => ['KeyboardRemove' => [], 'remove_keyboard' => true],
    "start" =>  [
        'keyboard' => [
            [
                ['text' => __('btn_name')],

            ],

        ],
        'resize_keyboard' => true
    ]

];
برای استفاده از پلاگین ها از متد btn استفاده کنید مثال برای استفاده در پلاگین ها :
<?php

namespace alirezax5\TelegramBase\Plugin;

use alirezax5\TelegramBase\App\Plugin\PluginBase;
use alirezax5\TelegramBase\App\Shared\SharedManagement;
use telegramBotApiPhp\Telegram;

class Start implements PluginBase
{
    public function getPriority(): int
    {
        return 1;
    }

    public function onMessage($update, Telegram $telegram)
    {
        $telegram->sendMessage($update->from->id, $update->text, reply_markup: btn('start'));
    }
    
}

دیتابیس

تنظیمات مربوط به دیتابیس را می‌توانید در فایل .env ویرایش کنید. از آن‌جایی که این بیس از illuminate/database استفاده می‌کند، می‌توانید برای نحوه‌ی کار با کوئری‌ها و مدل‌ها از مستندات لاراول کمک بگیرید:

🔗 https://laravel.com/docs/12.x/queries

به‌طور کلی، فایل‌های مربوط به دیتابیس را در پوشه‌ی Database قرار دهید. نمونه‌ی ساختار و تعریف دیتابیس:

<?php

namespace Database;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Capsule\Manager as DB;

class Users extends Model
{
    protected $table = 'users';
    protected $primaryKey = 'chatid';
    protected $casts = [
        'active' => 'boolean',
        'status' => 'boolean'
    ];
    public $timestamps = false;

    public static function checkAndInsert(int $chatid): bool
    {
        return !self::check($chatid) && self::insertOrIgnore(['chatid' => $chatid]);
    }

    public static function check(int $chatid): bool
    {
        return self::where('chatid', $chatid)->exists();
    }

    public static function getAllStatusActiveUser(bool $limit = true, int $page = 1, int $per = 20)
    {
        $query = self::where('status', true)->orderBy('id');
        return $limit ? $query->paginate($per, ['*'], 'page', $page) : $query->get();
    }
    public static function getAllStatusActiveUserByLang($lang = 'all',bool $limit = true, int $page = 1, int $per = 20)
    {
        if ($lang == 'all')
        $query = self::where('status', true)->orderBy('id');
        else
           $query = self::where('status', true)->where('lang', $lang)->orderBy('id');

        return $limit ? $query->paginate($per, ['*'], 'page', $page) : $query->get();
    }

    public static function getAllActiveUser(bool $limit = true, int $page = 1, int $per = 20)
    {
        $query = self::where('active', true)->orderBy('id', 'DESC');
        return $limit ? $query->paginate($per, ['*'], 'page', $page) : $query->get();
    }

    public static function getAll(bool $limit = true, int $page = 1, int $per = 20)
    {
        $query = self::orderBy('id');
        return $limit ? $query->paginate($per, ['*'], 'page', $page) : $query->get();
    }

    public static function getByRole(string $role)
    {
        return self::where('role', $role)->get(['id', 'chatid']);
    }

    public static function getAdmins()
    {
        return self::getByRole('admin');
    }

    public static function getCountByField(string $field, $value): int
    {
        return self::where($field, $value)->count();
    }


    public static function getCountAll(): int
    {
        return self::count();
    }

    public static function getCountAdmin(): int
    {
        return self::getCountByField('role', 'admin');
    }


    public static function getUser(int $chatid)
    {
        return self::where('chatid', $chatid)->first();
    }

    public static function getUserById($id)
    {
        return self::where('id', $id)->first();

    }

    public static function updateFieldByChatId(int $chatid, string $field, $value): bool
    {
        return self::where('chatid', $chatid)->update([$field => $value]);
    }



    public static function getRecentUsers()
    {
        $twentyFourHoursAgo = Carbon::today();

        $recentUsers = DB::table('users')
            ->where('create_at', '>=', $twentyFourHoursAgo)
            ->count();

        return $recentUsers;
    }

}
ساختار باید همین شکل باشه اما متد های مورد استفاده رو از لاراول مطالعه کنید

اشتراک داده ها بین پلاگین ها

طبق مثال زیر میتونید داده هارو بین پلاگین ها به اشتراک بگذارید.

داده ها بعد از اجرای یک آپدیت پاک می شوند.

<?php

use alirezax5\TelegramBase\App\Shared\SharedManagement;
#ثبت داده
SharedManagement::set('findCommand', 'na');
#دریافت داده
SharedManagement::get('findCommand', 'default');
#ثبت داده ای که بعد از انجام آپدیت پاک نشود.
SharedManagement::set('findCommand', 'na',true);

لاگ گیری

برای تنظیم مسیر فایل لاگ، نام فایل، و همچنین فعال یا غیرفعال کردن سیستم لاگ، می‌توانید مقادیر مربوطه را در فایل .env تغییر دهید.

نمونه‌ای از ثبت لاگ:

<?php

use alirezax5\TelegramBase\App\Logger\LogHandler;
 LogHandler::warning("warning");
 LogHandler::info('info');