توجه: این پروژه را ابتدا برای رباتهای خودم توسعه دادم و تصمیم گرفتم آن را منتشر کنم تا اگر برای شما هم کاربردی بود، بتوانید از آن استفاده کنید.
در صورت مشکل در کار با پروژه از هوش مصنوعی جیمنای با لینک زیر استفاده کنید. اکثر موارد مورد نیاز به gem داده شده است.
https://gemini.google.com/gem/13NSCMFOipq6HMvSRPZ2NvhKGzuYVrTDS?usp=sharing
هدف از توسعه این بیس، سادگی در کدنویسی و بهینهسازی برای سرویسدهی به تعداد بالای کاربر است.
سعی کردهام در حد امکان مصرف منابع (CPU / RAM / I/O) پایین باشد و ساختار پروژه قابل توسعه و انعطافپذیر باقی بماند.
در بخشهای مختلف پروژه از کمک هوش مصنوعی هم استفاده شده است. کمک گرفتن هیچ ایرادی ندارد — خروجی نهایی حاصل چند سال تجربه در ساخت رباتهای تلگرامی است و با کمک AI روی بهینگی و انتخاب بهترین ساختار تمرکز شده است.
- پشتیبانی از وبهوک, اجرای cli و کرون جاب
- پشتیبانی از صف بندی آپدیت ها
- پشتیبانی از منطق جدا سازی صب بندی و اجرای اسکریپت
- امکان توسعه چند زبانه
- امکان افزودن چندین دیتابیس
- سیستم کشینگ
- پشتیبانی از سیستم پلاگین
- امکان پوشه بندی پلاگین ها برای توسعه راحت ت
- لاگ گیری و بررسی راحت لاگ ها
- پشتیبانی از چندین درایور کشینگ, صف بندی و دیتابیس
- امکان استفاده از متغییر های داینامیک در زبان ها و دکمه
git clone https://github.com/alirezax5/baleBotBase.git
نیازه که git روی سیستم نصب باشه.
پس از کلون دستور زیر رو در پوشه ی سورس بزنید :
composer install
دانلود از بخش releases
پس از دانلود دستور زیر رو در پوشه ی سورس بزنید :
composer install
فرقی نمیکنه از کدوم روش استفاده کنید.
تمامی تنطیمات و کانفیگ سورس در فایل .env قرار داره
در حال حاضر سورس حالت های زیر رو داره :
- update_direct
در این حالت سورس با استفاده از polling آپدیت هارا دریافت میکنه و بدون صف بندی اجرا میکنه
- update_queue
در این حالت سورس با استفاده از polling آپدیت هارا دریافت میکنه و به صف آپدیت ها اضافه میکنه
- webhook_direct
در این حالت آپدیت هایی که از وبهوک میان به صورت مستقیم اجرا میشه
- webhook_queue
در این حالت آپدیت هایی که از وبهوک میان به صف برای اجرای به ترتیب اضافه میشن
- cronjob_update
در این حالت شما می توانید کرون جاب ست کنید و سورس با استفاده از polling و حلقه بی نهایت آپدیت هارو اجرا میکنه
- cronjob_queue
در این حالت کرون جاب با استفاده از polling اپدیت ها وارد صف می شن.
پیشنهاد اول راه اندازی اگر سرور دارید نصب rabbitmq, redis,Supervisor می باشد تا هم از قابلیت صف بندی استفاده کنید و هم از Supervisor برای مدیریت اجرا استفاده کنید:
در env :
BOT_MODE="update_queue"
QUEUE_SAVE_TYPE="rabbitmq"
CACHE_DRIVER="redis"و کانفیگ برای Supervisor :
[program:name]
command=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
توجه داشته باشید که مقدار های کانفیگ نمونه رو تغییر بدید.
در صورتی که از هاستینگی استفاده می کنید که از Supervisor استفاده نمیکنه:
BOT_MODE="webhook_direct"
QUEUE_SAVE_TYPE="json"
CACHE_DRIVER="array"در صورتی که دامنه ندارید میتونید از روش کرون جاب استفاده کنید.
اگر از صف بندی استفاده نمی کنید :
use alirezax5\BaleBase\App\Core;
include './vendor/autoload.php';
$core = new Core();
$core->run();اما اگر میخواید استفاده کنید به 2 فایل نیاز دارید :
use alirezax5\BaleBase\App\Core;
use alirezax5\BaleBase\App\Enum\CoreMode;
include './vendor/autoload.php';
$core = new Core(CoreMode::UPDATES_ONLY);
$core->runFetchQueueUpdate();use alirezax5\BaleBase\App\Core;
include './vendor/autoload.php';
$core = new Core();
$core->run();نکته : شما حتی می توانید دریافت و اجرا آپدیت رو در سرور ها مختلف قرار بدید.
سورس از پوشه بندی پلاگین ها پشتیبانی میکنه برای همین میتونید پلاگین هارو دسته بندی کنید فقط باید دقت داشته باشید که نام پوشه را در namespace کلاس قرار داده باشید
برای تنظیم کانفیگ ها میتوانید به فایل .env مراجعه کنید.
نمونه کد پلاگین :
<?php
namespace alirezax5\BaleBase\Plugin;
use alirezax5\BaleBase\App\Plugin\Contract\PluginInterface;
use baleBotPhp\bale;
class Start implements PluginInterface
{
public function getPriority(): int
{
return 1;
}
public function onMessage($update, bale $bale)
{
SharedManagement::set('findCommand', 'na');
$bale->sendMessage($update->from->id, $update->text,['reply_markup'=>btn('start')]);
}
public function onEditedMessage($update, bale $bale)
{
}
public function onCallbackQuery($update, bale $bale)
{
}
public function onInlineQuery($update, bale $bale)
{
}
public function onPollAnswer($update, bale $bale)
{
}
#.. سایر آپدیت ها
public function before($update, bale $bale)
{
}
public function after($update, bale $bale)
{
}
}نام متدها در ساختار PascalCase تعریف میشوند و هر نوع آپدیت، به متد مخصوص خودش ارسال خواهد شد. پارامترهای $update و $bale در تمام متدها بهصورت ثابت ارسال میشوند.
متد getPriority
این متد برای تعیین اولویت اجرای پلاگین استفاده میشود.
پیشنهاد میشود هنگام دریافت اطلاعات اولیهی کاربر، مقدار اولویت را روی 0 قرار دهید. اگر ترتیب اجرای پلاگین برای شما اهمیتی ندارد، مقدار 999 را تنظیم کنید.
متدهای before و after
before قبل از اجرای متد مربوط به آپدیت فراخوانی میشود.
after پس از اجرای متد آپدیت اجرا خواهد شد.
این ساختار به شما اجازه میدهد منطق پیشپردازش و پسپردازش را بهصورت مجزا و تمیز مدیریت کنید.
بیس به شما این امکان رو میده یک ربات چندزبانه رو توسعه بدید در فایل .env شما می توانید زبان پیشفرض,مسیر زبان ها و مدت زمان کش زبان ها را تنظیم نمایید. پیشفرض در پوشه Language و با پسوند php باید متون خود رو ذخیره کنید.
برای فرخوانی متن و تغییر زبان می توانید از مثال زیر استفاده کنید :
<?php
# دریافت ترجمه براساس نام کلید در فایل json
__('name');
# برای تنظیم زبان
Language::getInstance()->setLanguageDir( 'fa');
# برای جایگزین کردن مقدار خاص در متن ترجمه
__('name',['id'=>1]);return [
"key" => "value",
"key2" => "value #id",
];دکمهها بهصورت پیشفرض در فایل btn.php در ریشهی سورس قرار دارند. در صورت نیاز، میتوانید مسیر این فایل را از طریق تنظیمات .env تغییر دهید.
نمونهی فایل تعریف دکمهها:
<?php
return [
"remove" => ['KeyboardRemove' => [], 'remove_keyboard' => true],
"start" => [
'keyboard' => [
[
['text' => __('btn_name')],
],
],
'resize_keyboard' => true
]
];<?php
<?php
namespace alirezax5\BaleBase\Plugin;
use alirezax5\BaleBase\App\Plugin\Contract\PluginInterface;
use baleBotPhp\bale;
class Start implements PluginInterface
{
public function getPriority(): int
{
return 1;
}
public function onMessage($update, bale $bale)
{
SharedManagement::set('findCommand', 'na');
$bale->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;
protected $connection = 'main';
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');برای تنظیم تنظیمات کش میتونید به .env مراجعه کنید نمونه کد:
<?php
namespace Functions;
use alirezax5\BaleBase\App\Cache\CacheManager;
use alirezax5\BaleBase\App\Environment\EnvHandler;
use baleBotPhp\bale;
class CacheData
{
static public function getchat(bale $bale, $chatid)
{
$cacheKey = EnvHandler::get('CACHE_PREFIX') . 'getchat_' . $chatid;
if (!CacheManager::isInitialized())
return $bale->getchat($chatid);
if (CacheManager::has($cacheKey)) {
return CacheManager::get($cacheKey);
}
$getchat = $bale->getchat($chatid);
CacheManager::put($cacheKey, $getchat, 300);
return $getchat;
}