PHP code example of thexerc / laravel-bale

1. Go to this page and download the library: Download thexerc/laravel-bale library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

thexerc / laravel-bale example snippets


return [
    'default' => env('BALE_BOT', 'default'),

    'bots' => [
        'default' => [
            'token'          => env('BALE_TOKEN'),
            'webhook_secret' => env('BALE_WEBHOOK_SECRET'),
            'mode'           => env('BALE_MODE', 'webhook'),
        ],
    ],

    'api_base_url' => 'https://tapi.bale.ai/bot',
    'file_base_url' => 'https://tapi.bale.ai/file/bot',

    'webhook_prefix' => env('BALE_WEBHOOK_PREFIX', 'bale/webhook'),

    'polling' => [
        'timeout' => 30,
        'limit'   => 100,
        'sleep'   => 1,
    ],

    'miniapp' => [
        'auth_route_enabled' => true,
        'init_data_ttl'      => 3600,
    ],
];

// Pin a message (silently, no notification)
Bale::pinChatMessage($chatId, $messageId, disableNotification: true);

// Unpin a specific message
Bale::unpinChatMessage($chatId, $messageId);

// Unpin the most recently pinned message
Bale::unpinChatMessage($chatId);

// Unpin all pinned messages
Bale::unpinAllChatMessages($chatId);

Bale::setChatTitle($chatId, 'My Awesome Group');
Bale::setChatDescription($chatId, 'The official support group.');

// setChatPhoto :getChatMembersCount($chatId);

// Mute a user
Bale::restrictChatMember($chatId, $userId, [
    'can_send_messages'       => false,
    'can_send_media_messages' => false,
]);

// Restore permissions
Bale::restrictChatMember($chatId, $userId, [
    'can_send_messages'       => true,
    'can_send_media_messages' => true,
]);

// Temporary restriction (lifts after 1 hour)
Bale::restrictChatMember($chatId, $userId, ['can_send_messages' => false], untilDate: time() + 3600);

// Promote to admin with specific rights
Bale::promoteChatMember($chatId, $userId, [
    'can_delete_messages' => true,
    'can_pin_messages'    => true,
    'can_invite_users'    => true,
]);

// Demote back to member
Bale::promoteChatMember($chatId, $userId, [
    'can_delete_messages' => false,
    'can_pin_messages'    => false,
]);

$link     = Bale::exportChatInviteLink($chatId);

$linkInfo = Bale::createChatInviteLink($chatId, [
    'name'         => 'Summer Campaign',
    'expire_date'  => time() + 86400,
    'member_limit' => 50,
]);

Bale::revokeChatInviteLink($chatId, $linkInfo['invite_link']);

Bale::editMessageMedia($chatId, $messageId, [
    'type'    => 'photo',
    'media'   => $newFileId,
    'caption' => 'Updated caption',
]);

// Swap media and update the inline keyboard simultaneously
$keyboard = (new InlineKeyboard())->button('Download', callbackData: 'dl:42');
Bale::editMessageMedia($chatId, $messageId, ['type' => 'photo', 'media' => $fileId], $keyboard);

// config/bale.php
'bots' => [
    'default' => [
        'token' => env('BALE_TOKEN'),
    ],
    'support' => [
        'token' => env('BALE_SUPPORT_TOKEN'),
    ],
    'shop' => [
        'token'          => env('BALE_SHOP_TOKEN'),
        'webhook_secret' => env('BALE_SHOP_SECRET'),
    ],
],

use TheXERC\Bale\Facades\Bale;

public function boot(): void
{
    Bale::onCommand('start', \App\Bale\Handlers\StartHandler::class);
    Bale::onCommand('help',  \App\Bale\Handlers\HelpHandler::class);
}

use TheXERC\Bale\Facades\Bale;

$message = Bale::sendMessage($chatId, 'Hello, world!');

// Markdown
Bale::sendMessage($chatId, '*Bold* and _italic_', [
    'parse_mode' => 'Markdown',
]);

// HTML
Bale::sendMessage($chatId, '<b>Bold</b> and <i>italic</i>', [
    'parse_mode' => 'HTML',
]);

Bale::sendMessage($chatId, 'Got it!', [
    'reply_to_message_id' => $message->messageId,
]);

Bale::deleteMessage($chatId, $messageId);

Bale::forwardMessage($toChatId, $fromChatId, $messageId);

Bale::editMessageText($chatId, $messageId, 'Updated text');

use TheXERC\Bale\Facades\Bale;
use TheXERC\Bale\Keyboards\InlineKeyboard;
use TheXERC\Bale\Keyboards\InlineKeyboardButton;
use TheXERC\Bale\Objects\CopyTextButton;

$keyboard = (new InlineKeyboard())
    ->row(
        new InlineKeyboardButton('✅ Confirm', callbackData: 'confirm'),
        new InlineKeyboardButton('❌ Cancel',  callbackData: 'cancel'),
    )
    ->row(
        new InlineKeyboardButton('🌐 Visit Website', url: 'https://example.com'),
    )
    ->row(
        // Opens a Mini App
        new InlineKeyboardButton('🚀 Open App', webAppUrl: 'https://your-app.com/miniapp'),
    )
    ->row(
        // Copy text to clipboard when tapped
        new InlineKeyboardButton('📋 Copy Code', copyText: new CopyTextButton('DISCOUNT50')),
    );

Bale::sendMessage($chatId, 'Choose an option:', [
    'reply_markup' => $keyboard,
]);

use TheXERC\Bale\Keyboards\ReplyKeyboard;
use TheXERC\Bale\Keyboards\ReplyKeyboardButton;

$keyboard = (new ReplyKeyboard())
    ->row('📦 My Orders', '🔍 Search')
    ->row(
        new ReplyKeyboardButton('📞 Share Contact', requestContact: true),
        new ReplyKeyboardButton('📍 Share Location', requestLocation: true),
    )
    ->row(
        // Open a Mini App directly from a reply keyboard button
        new ReplyKeyboardButton('🚀 Open App', webAppUrl: 'https://your-app.com/miniapp'),
    )
    ->resize()
    ->oneTime();

Bale::sendMessage($chatId, 'What would you like to do?', [
    'reply_markup' => $keyboard,
]);

use TheXERC\Bale\Keyboards\ReplyKeyboard;

Bale::sendMessage($chatId, 'Keyboard removed.', [
    'reply_markup' => ReplyKeyboard::remove(),
]);

// By file_id (already on Bale servers — recommended)
Bale::sendPhoto($chatId, 'AgACAgIAAxkBAAI...', ['caption' => 'A nice photo']);

// By local file path (uploaded via multipart)
Bale::sendPhoto($chatId, '/path/to/photo.jpg', ['caption' => 'Uploaded photo']);

// By URL
Bale::sendPhoto($chatId, 'https://example.com/photo.jpg');

Bale::sendDocument($chatId, '/path/to/report.pdf', ['caption' => 'Monthly report']);

Bale::sendVideo($chatId, '/path/to/video.mp4');

Bale::sendAudio($chatId, '/path/to/audio.mp3');
Bale::sendVoice($chatId, '/path/to/voice.ogg');

Bale::sendLocation($chatId, latitude: 35.6892, longitude: 51.3890);

Bale::sendContact($chatId, phoneNumber: '+989123456789', firstName: 'Ali');

Bale::sendAnimation($chatId, '/path/to/animation.gif', ['caption' => 'Look at this!']);

// Using file_ids (no upload, fastest)
$messages = Bale::sendMediaGroup($chatId, [
    ['type' => 'photo', 'media' => $fileId1, 'caption' => 'First photo'],
    ['type' => 'photo', 'media' => $fileId2],
]);

// Uploading local files (multipart, attach:// handled automatically)
$messages = Bale::sendMediaGroup($chatId, [
    ['type' => 'photo', 'media' => '/path/to/photo1.jpg', 'caption' => 'Uploaded 1'],
    ['type' => 'photo', 'media' => '/path/to/photo2.jpg'],
]);

// Mixed (first local, second already uploaded)
$messages = Bale::sendMediaGroup($chatId, [
    ['type' => 'photo', 'media' => '/path/to/new.jpg'],
    ['type' => 'photo', 'media' => $existingFileId],
]);

Bale::copyMessage(toChatId: $chatId, fromChatId: $sourceChatId, messageId: $msgId);

Bale::sendChatAction($chatId, 'typing');
// Other actions: upload_photo | record_video | upload_video |
//                record_voice | upload_voice | upload_document | find_location

// Get a File object containing the file_path
$file = Bale::getFile($fileId);

// Build the download URL
$url = Bale::getFileUrl($file->filePath);

// Download the file to a local path
$localPath = Bale::downloadFile($file, storage_path('app/downloads/myfile.pdf'));

public function handle(Update $update, BotApi $bot): void
{
    $document  = $update->message->document; // Document object
    $file      = $bot->getFile($document->fileId);
    $localPath = $bot->downloadFile($file, storage_path("app/uploads/{$document->fileName}"));

    $bot->sendMessage($update->getChatId(), "File saved: {$document->fileName}");
}

use TheXERC\Bale\Facades\Bale;

public function boot(): void
{
    // Class-based handler
    Bale::onCommand('start', \App\Bale\Handlers\StartHandler::class);

    // Closure handler
    Bale::onCommand('ping', function (Update $update, BotApi $bot) {
        $bot->reply($update, 'Pong! 🏓');
    });
}

namespace App\Bale\Handlers;

use TheXERC\Bale\BotApi;
use TheXERC\Bale\Objects\Update;
use TheXERC\Bale\Router\Handler;

class StartHandler extends Handler
{
    public function handle(Update $update, BotApi $bot): void
    {
        $from = $update->getFrom();

        $bot->sendMessage(
            $update->getChatId(),
            "👋 Hello {$from->getFullName()}!\n\nWelcome to the bot. Use /help to see commands.",
        );
    }
}

class StartHandler extends Handler
{
    public function __construct(private UserRepository $users) {}

    public function handle(Update $update, BotApi $bot): void
    {
        $from = $update->getFrom();
        $this->users->firstOrCreate(['bale_id' => $from->id], [
            'name' => $from->getFullName(),
        ]);

        $bot->reply($update, 'Welcome back!');
    }
}

// Match any message (no pattern)
Bale::onMessage(function (Update $update, BotApi $bot) {
    $bot->reply($update, 'You said: '.$update->message->text);
});

// Match only messages containing a phone number
Bale::onMessage(\App\Bale\Handlers\PhoneHandler::class, '/\+?\d{10,}/');

// Handle all callback queries
Bale::onCallbackQuery(function (Update $update, BotApi $bot) {
    $data = $update->callbackQuery->data;
    $bot->answerCallbackQuery($update->callbackQuery->id, "You clicked: {$data}");
});

// Filter by data prefix
Bale::onCallbackQuery(\App\Bale\Handlers\OrderHandler::class, 'order:');
// Handles queries like "order:42", "order:cancel:5", etc.

public function handle(Update $update, BotApi $bot): void
{
    $query  = $update->callbackQuery;
    $chatId = $query->message->chat->id;
    $msgId  = $query->message->messageId;

    // Remove the inline keyboard after the click
    $bot->editMessageText($chatId, $msgId, 'Order confirmed ✅');
    $bot->answerCallbackQuery($query->id);
}

use TheXERC\Bale\Events\UpdateReceived;
use TheXERC\Bale\Events\MessageReceived;
use TheXERC\Bale\Events\EditedMessageReceived;
use TheXERC\Bale\Events\CommandReceived;
use TheXERC\Bale\Events\CallbackQueryReceived;
use TheXERC\Bale\Events\PreCheckoutQueryReceived;
use TheXERC\Bale\Events\PaymentReceived;

protected $listen = [
    UpdateReceived::class            => [App\Listeners\LogAllUpdates::class],
    MessageReceived::class           => [App\Listeners\HandleMessage::class],
    EditedMessageReceived::class     => [App\Listeners\HandleEditedMessage::class],
    CommandReceived::class           => [App\Listeners\HandleCommand::class],
    CallbackQueryReceived::class     => [App\Listeners\HandleCallback::class],
    PreCheckoutQueryReceived::class  => [App\Listeners\ApproveCheckout::class],
    PaymentReceived::class           => [App\Listeners\FulfillOrder::class],
];

Bale::onEditedMessage(function (Update $update, BotApi $bot) {
    $edited = $update->editedMessage;
    $bot->sendMessage($edited->chat->id, "You edited: {$edited->text}");
});

namespace App\Listeners;

use TheXERC\Bale\Events\PaymentReceived;

class FulfillOrder
{
    public function handle(PaymentReceived $event): void
    {
        $payment = $event->payment;       // SuccessfulPayment object
        $chatId  = $event->update->getChatId();

        // $payment->invoicePayload holds whatever you put in sendInvoice()
        Order::where('payload', $payment->invoicePayload)->fulfill();

        $event->bot->sendMessage($chatId, "✅ Payment received! Thank you.");
    }
}

use TheXERC\Bale\Facades\Bale;
use TheXERC\Bale\Payments\LabeledPrice;

// 1. Send an invoice
Bale::sendInvoice(
    chatId:        $chatId,
    title:         'Premium Subscription',
    description:   '30 days of access to all features.',
    payload:       'sub_' . $userId,
    providerToken: env('BALE_PAYMENT_TOKEN'),
    prices:        [
        new LabeledPrice('Subscription', 50_000),
    ],
);

// 2. Handle pre_checkout_query — MUST answer within 10 seconds
Bale::onPreCheckoutQuery(function ($update, $bot) {
    $query = $update->preCheckoutQuery;

    // Validate stock, eligibility, etc.
    $ok = true;

    $bot->answerPreCheckoutQuery($query->id, ok: $ok);
    // Or reject with a reason:
    // $bot->answerPreCheckoutQuery($query->id, ok: false, errorMessage: 'Item out of stock');
});

// 3. Fulfil the order after successful payment (via PaymentReceived event — see above)

use TheXERC\Bale\Events\PreCheckoutQueryReceived;

class ApproveCheckout
{
    public function handle(PreCheckoutQueryReceived $event): void
    {
        $event->bot->answerPreCheckoutQuery($event->preCheckoutQuery->id, ok: true);
    }
}

// Returns a typed Transaction object — no chat_id needed
$tx = Bale::inquireTransaction('txn_abc123');
// $tx->id | $tx->status | $tx->amount | $tx->currency | $tx->date

// delay_seconds is rId: $userId, delaySeconds: 5);

$url = Bale::createInvoiceLink(
    title:         'Premium Plan',
    description:   '30-day access',
    payload:       'plan_premium',
    providerToken: env('BALE_PAYMENT_TOKEN'),
    prices:        [new LabeledPrice('Plan', 50_000)],
);

Bale::onMessage(function (Update $update, BotApi $bot) {
    $webAppData = $update->message->webAppData;

    if ($webAppData) {
        // $webAppData->data        — raw string sent by the Mini App
        // $webAppData->buttonText  — label of the keyboard button that opened the app
        // $webAppData->json()      — decoded array if the data was JSON

        $payload = $webAppData->json();
        $bot->sendMessage($update->getChatId(), "Received order #{$payload['order_id']}");
    }
});

use TheXERC\Bale\MiniApp\MiniAppHelper;

public function store(Request $request): JsonResponse
{
    $initData = $request->header('X-Bale-Init-Data');
    $token    = config('bale.bots.default.token');

    $result = MiniAppHelper::validate($initData, $token, ttl: 3600);

    if (! $result['valid']) {
        return response()->json(['error' => $result['error']], 401);
    }

    $user = $result['user']; // ['id' => ..., 'first_name' => ..., ...]

    // Proceed with the authenticated user
}

// app/Http/Middleware/ValidateBaleUser.php
use TheXERC\Bale\MiniApp\MiniAppHelper;

class ValidateBaleUser
{
    public function handle(Request $request, Closure $next): Response
    {
        $initData = $request->header('X-Bale-Init-Data');
        $result   = MiniAppHelper::validate($initData, config('bale.bots.default.token'));

        if (! $result['valid']) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        // Attach the user to the request for downstream use
        $request->attributes->set('bale_user', $result['user']);

        return $next($request);
    }
}

use TheXERC\Bale\Facades\Bale;

// Default bot
Bale::sendMessage($chatId, 'Hello from the default bot');

// Named bot
Bale::bot('support')->sendMessage($chatId, 'Hello from the support bot');

// Register handlers per bot
Bale::bot('shop')->onCommand('start', \App\Bale\Shop\StartHandler::class);
Bale::bot('shop')->onCommand('catalog', \App\Bale\Shop\CatalogHandler::class);

use TheXERC\Bale\Exceptions\BaleApiException;

try {
    Bale::sendMessage($chatId, $text);
} catch (BaleApiException $e) {
    if ($e->isRateLimited()) {
        // $e->retryAfter — seconds to wait before retrying
        sleep($e->retryAfter);
        Bale::sendMessage($chatId, $text); // retry
    } elseif ($e->migrateToChatId !== null) {
        // Group was upgraded to supergroup — update stored chat ID
        $newChatId = $e->migrateToChatId;
    } else {
        throw $e;
    }
}
bash
php artisan vendor:publish --tag=bale-config
bash
php artisan vendor:publish --tag=bale-stubs
bash
php artisan vendor:publish --tag=bale-assets
bash
php artisan boost:install
bash
php artisan bale:copilot-setup
bash
# Overwrite existing files without prompting
php artisan bale:copilot-setup --force
bash
php artisan bale:webhook set
bash
php artisan bale:poll
bash
php artisan bale:poll --bot=support --timeout=30 --limit=100 --sleep=1
bash
php artisan bale:make-handler StartHandler
bash
php artisan bale:webhook set --bot=shop
php artisan bale:webhook set --bot=support
bash
php artisan bale:poll
php artisan bale:poll --bot=support
php artisan bale:poll --timeout=60 --limit=50
bash
php artisan bale:make-handler StartHandler
# Creates app/Bale/Handlers/StartHandler.php