Appearance
Commands
Since essence v0.10.0, /command messages are routed through CommandBus (TelegramBotEssentials\Essence\Telegram\Commands\CommandBus) — the same dependency-injected, auto-discovered handler pattern used by CallbackQueries and StateAnswers, in place of the underlying telegram-bot-sdk's own command bus.
Defining a command
php
namespace App\Telegram\Commands\Member;
use TelegramBotEssentials\Essence\Enums\Roles;
use TelegramBotEssentials\Essence\Telegram\Commands\Command;
class StartCommand extends Command
{
protected string $name = 'start';
protected int $perm = Roles::MEMBER->value;
// Since v0.12.0: an overridable method calling __() directly, not a
// stored property — one instance renders in every bot's locale.
protected function description(): ?string
{
return __('app.telegram.start.description');
}
public function handle(): ?bool
{
// ...
return true;
}
}Scaffold one with:
bash
php artisan tbe:make:command StartCommand --command-name=start --description="Start using the bot"
php artisan tbe:make:command BanUser --admin--admin puts the class under app/Telegram/Commands/Admin (default: Member) and pre-fills $perm accordingly; --command-name= sets the Telegram-facing name (default: snake_case of the class name).
Auto-discovery
Same convention as CallbackQueries, StateAnswers, and ReplyKeys — commands are picked up automatically, no manual registration needed. Since v0.12.0 TelegramBotServiceProvider scans, via loadCommands($path), once per worker boot (not on every request):
app/Telegram/Commands/Member/*.phpandapp/Telegram/Commands/Admin/*.phpin the host app- Essence's own built-in commands (currently just
HelpCommand)
config('tbe-essence.commands') — handler registration vs. the command menu
Earlier versions registered command handlers by listing SDK Telegram\Bot\Commands\Command subclasses in config/tbe-essence.php's commands array, wired via wHook()->api()->addCommand()/processCommand(). That path is gone — handlers are now auto-discovered from the directories above.
The commands key itself still exists, repurposed: it is the list tbe:set-webhook sends to Telegram via setMyCommands() to populate the client's command menu (the / autocomplete list). Its entries must now be TelegramBotEssentials\Essence\Telegram\Commands\Command subclasses (essence resolves each through the container and reads getName()/getDescription()). Keep it populated with the commands you want listed; it does not affect which commands actually work — an auto-discovered command handles messages whether or not it appears here.
Param binding
Reflection-based, same as CallbackQuery::handle() — not the SDK's pattern/$this->argument() style. A real /command arg1 arg2 message is whitespace-split and the parts passed positionally into handle()'s parameters:
php
public function handle(string $arg1, ?string $arg2 = null): ?boolTyped Eloquent model parameters are resolved the same way CallbackQueries resolve them — looked up by id unless the command declares $bindings:
php
protected array $bindings = [
BotUser::class => 'telegram_user_peer_id',
];
public function handle(BotUser $botUser): ?boolTriggering a command programmatically — commandBus()->route()
Any other bus handler (a CallbackQuery, StateAnswer, ReplyKey — even another Command) can dispatch into a registered command exactly as if the user had typed it:
php
commandBus()->route('start');
commandBus()->route('start', ['some_arg']);This is how, since Settings v0.0.14, ChannelLockQuery re-runs /start right after a user clears the channel-join gate — the user never has to retype the command themselves.
route():
- Throws
TbeLogicExceptionon an unknown command name/alias — same fail-fast convention asCallbackQueryBus's unregistered-type handling. This also applies to real, unmatched/whatevermessages (unified throughprocessCommands()), which is a behavior change from the old SDK bus's silent fallback. - Fires
BotCommandHandled(see Events & Listeners) whether the trigger was a real message or a programmatic call — the controller no longer fires this event itself. - Does not cancel any pending state/process on its own — that stays the caller's responsibility. The real-message path (
processCommands(), called fromTelegramWebhookController) still cancels state/process at the controller level before routing, unchanged.
The built-in /help command
HelpCommand (alias /listcommands) lists every currently registered, enabled, permission-visible command by reading commandBus()->getCommands() — it stays accurate automatically as you add or remove commands, no maintenance required.
Reference
| Method | Description |
|---|---|
commandBus() | Resolve the CommandBus singleton |
addCommand(CommandInterface|string $command) | Register one command (FQCN or instance) |
addCommands(iterable $commands) | Register several |
removeCommand(string $name) / removeCommands(array $names) | Unregister |
getCommands() | array<string, CommandInterface> keyed by command name |
route(string $name, array $params = []) | Dispatch a command as if the user typed it |
processCommands() | Parses the current update's message text and calls route(); used internally by TelegramWebhookController |
Source: src/Telegram/Commands/{CommandInterface,Command,CommandBus}.php