more-cores / discord-commands
Requires
- php: >=8.1
Requires (Dev)
- mockery/mockery: ^1.0
- phpunit/phpunit: ^10.0
- simplito/elliptic-php: ^1.0.10
Suggests
- simplito/elliptic-php: required when using this package to respond to command interactions.
README
使用此库,您可以构建 Discord 机器人命令,处理来自 Discord 的传入交互并回复消息。
目录
安装
composer require more-cores/discord-commands
创建消息
创建 webhook 消息(它们有更高的速率限制)
$message = new WebhookMessage(); $message->setContent($content); $embed = new Embed(); $embed->setTitle($title); $embed->setDescription($description); $embed->setUrl($url); $embed->setTimestamp($dateTime); $embed->setColor($color); $message->addEmbed($embed); $message->toJson(); // valid json ready to be sent to Discord via a Webhook
或使用标准消息
$message = new Message(); $message->setContent($content); $message->addEmbed($embed);
提及
Message
和 WebhookMessage
都提供提及角色的能力。
// appends the mention to the previously set content. Setting the content again overrides mentions $message->mentionRole($roleId); $message->isRoleMentioned($roleId); $message->hasMentions();
丰富
某些信息可以在 Discord 端进行丰富,例如链接到通道和用户本地时间的时间戳。
链接到通道
use \DiscordCommands\Messages\Message; $message->setContent('Instead of this channel, go to '.Message::linkChannel(34835835834));
链接到特殊通道
use \DiscordCommands\Messages\Message; use \DiscordCommands\Messages\GuildNavigation; $message->setContent('Help find new channels in '.Message::linkInGuild(GuildNavigation::CHANNEL_BROWSER));
嵌入时间戳
use \DiscordCommands\Messages\Message; use \DiscordCommands\Messages\TimestampFormat; $message->setContent('The contest runs until '.Message::timestamp(time()+6000)); // You can also customize formatting $message->setContent('The contest runs until '.Message::timestamp(time()+600, TimestampFormat::SHORT_TIME));
作者
// define an embed author using shorthand $embed->setAuthor($name); // and optionally specify specific attributes $embed->setAuthor($name, $url); // define an embed author by object $author = new Author(); $author->setName($name); $embed->setAuthor($author);
字段
// define an embed video using shorthand $embed->addField($fieldName, $fieldValue); // and optionally specify whether it's inline (default to false) $embed->addField($fieldName, $fieldValue, $inline = true); // define an embed field by object $field = new Field(); $field->setName($name); $field->setValue($value); $embed->setVideo($field);
图片
$embed->setImageUrl($imageUrl);
缩略图
$embed->setThumbnailUrl($thumbnailUrl);
页脚
// define an embed footer using shorthand $embed->setFooter($text, $iconUrl); // and optionally specify specific attributes $embed->setFooter($urlToImage, $width, $height); // define an embed thumbnail by object $thumbnail = new Thumbnail(); $thumbnail->setText($text); $thumbnail->setUrl($urlToImage); $embed->setFooter($thumbnail);
组件
按钮
为每种按钮类型使用专用类。因为按钮是非交互的,所以您需要将它们包装在 ActionRow
中以添加到消息。您可以使用缩写 actionRow()
来完成此操作。
$message->actionRow(new SuccessButton('button-id', 'Approve it')); $message->actionRow(new DangerButton('button-id', 'Reject it')); $message->actionRow(new PrimaryButton('button-id', 'Something else')); $message->actionRow(new SecondaryButton('button-id', 'Something else')); $message->actionRow(new LinkButton('https://mysite.com', 'My Site'));
选择菜单
$message->addComponent(new StringSelectMenu($menuId, [ new Option('Option 1', 'option-1'), new Option('Option 2', 'option-2'), ]));
角色选择菜单
$message->addComponent(new RoleSelectMenu($menuId));
可提及选择菜单
$message->addComponent(new MentionableSelectMenu($menuId));
可提及选择菜单
$message->addComponent(new UserSelectMenu($menuId));
通道选择菜单
$message->addComponent(new ChannelSelectMenu($menuId));
您还可以限制可以选择的通道类型
$message->addComponent(new ChannelSelectMenu($menuId, [ channelTypes: [ Channel::TYPE_GUILD_TEXT, Channel::TYPE_GUILD_VOICE, ], ]));
文本输入
$message->addComponent(new ShortInput($fieldId, 'First Name')); $message->addComponent(new ParagraphInput($fieldId, 'Dating Profile'));
创建命令
聊天命令
要创建聊天命令,创建一个类似于这样的类
class MyCommand extends ChatInputCommand { public const NAME = 'my-command'; public function __construct() { parent::__construct( name: self::NAME, description: 'This is executable by the higher ups in the guild', availableInDms: false, defaultMemberPermissions: [ Permission::ADMINISTRATOR, Permission::MANAGE_GUILD, ] ); } }
然后您只需运行 (new MyCommand())->jsonSerialize()
来生成同步您的命令与 Discord 所需的 JSON 有效负载。
处理命令交互
您可以使用我们的工厂来初始化表示 Discord 内部传入交互的对象。以下是一个使用 Laravel 请求对象的示例
$factory = new InteractionTypeFactory(); $interaction = $factory->make($request->json('type'), $request->json());
请求验证
Discord 要求您验证其系统传入请求的签名。您必须设置此签名验证,才能配置交互端点。
您需要运行 composer require simplito/elliptic-php
以添加必要的依赖项,然后您可以像这样验证请求
$verifier = new SignatureVerifier(); if (!$verifier->verify( rawBody: file_get_contents('php://input'), publicKey: getenv('DISCORD_BOT_PUBLIC_KEY'), signature: $_SERVER['HTTP_X_SIGNATURE_ED25519'], timestamp: $_SERVER['HTTP_X_SIGNATURE_TIMESTAMP'], )) { return http_response_code(401); } // Everything's good, do the rest here
Laravel 中间件
对于 Laravel 应用程序,我们已包含一个中间件以帮助您完成此操作。您可以在 routes/api.php
中配置一个路由,并用我们的验证中间件包装它
Route::group([ 'prefix' => 'discord', 'namespace' => 'Api\Discord', 'middleware' => LaravelDiscordSignatureVerificationMiddleware::class, ], function () { Route::post( 'webhook/command/interactions', 'CommandInteractionController@userInteracted' ); });
然后在您的控制器中,确保您对 Ping
进行响应...
public function userInteracted( Request $request, InteractionTypeFactory $interactionTypeFactory ) { $interaction = $interactionTypeFactory->make($request->json('type'), $request->all()); if ($interaction instanceof Ping) { return new Pong(); } // generate other responses based on the inbound interaction }
响应交互
您可以通过发送消息、显示模态等方式响应用户交互。确保阅读 Discord 文档(例如,您的应用程序有 3 秒的时间来响应用户交互)。
模态
要显示模态,您可以简单地使用您的模态对象对交互 HTTP 请求进行响应
显示模态
$modal = new ShowModal( id: 'something', title: "Add new game/software for voting", ); $modal->actionRow( new ShortInput( id: 'field-1', label: 'My Field', ) ); $modal->actionRow( new ShortInput( id: 'field-2', label: 'My Field 2', ), ); return $modal->jsonSerialize();
处理模态反馈
要处理模态反馈,请确保您正在处理 ModalSubmitted
事件。
以下示例中,$commandFactory
不是由此包提供的,但与您的领域相关。您可以按自己的意愿定制此过程。
public function interactions( Request $request, InteractionTypeFactory $interactionTypeFactory, CommandFactory $commandFactory, ) { $interaction = $interactionTypeFactory->make($request->json('type'), $request->all()); if ($interaction instanceof Ping) { return new Pong(); } if ($interaction instanceof ModalSubmitted) { $command = $commandFactory->makeByModal($interaction->modal()->id()); return $command->whenSubmitted($interaction, $interaction->modal()); } throw new RuntimeException('Command interaction went unhandled'); }
在我的情况下,我喜欢一个类包含关于命令发生的所有详细信息。因此,我添加了一个 whenSubmitted
方法,如下所示
public function whenSubmitted( ModalSubmitted $interaction, SubmittedModal $modal, ): CommandResponse { Log::info('modal submitted', [ 'modal' => $modal->id(), 'value' => $modal->fieldValue('field-2'), ]); return new ReplyWithMessage( content: "You're all done!", onlyVisibleToCommandIssuer: true, ); }
关键要点是 Discord 要求 对模态提交进行交互响应。因此,您需要做出响应。