io / discord
Discord bots in Io apps: slash commands, buttons, modals and events as attributes on your own classes, running on Tenma with tenma/discord.
Requires
- php: >=8.5
- io/framework: ^0.1.0
- tenma/discord: ^0.1.0
Requires (Dev)
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^13.3
- tenma/stubs: ^0.2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A Discord bot inside a Io app: slash commands, buttons, modals and gateway events are attributes on your own classes, with Io's dependency injection. The bot runs on Tenma next to the app's routes, through tenma/discord.
use Tenma\Discord\Interactions\Command; use Io\Discord\Attributes\{Describe, SlashCommand}; use Io\Discord\DiscordPlugin; use Io\App; final class GreetCommands { #[SlashCommand( description: 'Says hello' )] public function hello(Command $command, #[Describe('Who to greet')] ?string $name): string { return 'Hello ' . ($name ?? $command->user->name); } } $app = App::create(); $app->plugin(new DiscordPlugin([GreetCommands::class])); $app->attach([]); $app->serve();
Install
composer require io/discord vendor/bin/tenma-discord install DISCORD_TOKEN=... tenma index.php
tenma-discord install downloads the native plugin and adds it to the project's tenma.json. The token comes from
DISCORD_TOKEN in the Environment, or from
DiscordOptions:
$app->configure(function (DiscordOptions $discord) { $discord->intents = Intents::Default | Intents::GuildMessages | Intents::MessageContent; });
The bot connects only when the app serves under Tenma: under FPM, in tests and in CLI tasks
(tenma index.php db:migrate) it stays offline.
Handlers
A handler class is a plain class; the plugin takes the list. Each call gets its own copy of the class, made from the
container's instance with its #[Inject] services, as a controller does per request. What a method returns (a string
or a Tenma\Discord\Message) is the reply.
#[SlashCommand(description: ..., name: ...)]: the method is a slash command, named after the method unlessnamesays otherwise. Itsstring,int,floatandboolparameters are the command's options:$stationUrlis the optionstation_url, a nullable parameter or one with a default is optional.#[Describe('Text')]gives an option its description;#[Describe('Text', suggest: 'method')]lets a method of the same class suggest values while the user types (it gets anAutocompleteand returns values or names => values).#[ButtonClick('vote')]: clicks on buttons with the idvoteorvote:....#[ModalSubmit('feedback')]: sent modals; astringparameter gets the field of its name.#[OnEvent]: a listener for the event its parameter's type names,ready(Ready $ready).
A parameter of the interaction's type (Command, Click, Submit) gets the interaction, one of type Bot the bot.
The tenma/discord README covers messages, embeds,
buttons, modals, voice and the REST API.
The bot in the app
The plugin registers the Tenma\Discord\Bot in the container, so a route or a service can post into Discord:
#[Controller('/announce')] final class AnnounceController { #[Inject] private Bot $bot; #[Post] public function announce(#[Query] string $channel, #[Query] string $text): void { $this->bot->rest->send($channel, $text); } }
Another plugin adds its own handler classes in boot(Handlers $handlers) with $handlers->add(...).
Under tenma --workers N every worker runs the bot; each gets the events of its share of the guilds. State that all
workers need belongs in Postgres or Redis.
Demo
demo/ is a radio bot as a small Io project: /radio with station suggestions plays in your voice
channel, and a status page shows what plays where.
cd demo && composer install && vendor/bin/tenma-discord install cp .env.example .env # and fill in DISCORD_TOKEN tenma index.php # the bot, and the status page on http://localhost:8080
License
MIT or Apache-2.0, at your option.