more-cores/discord-commands

0.5.2 2023-10-22 12:36 UTC

This package is auto-updated.

Last update: 2024-09-06 20:31:13 UTC


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);

提及

MessageWebhookMessage 都提供提及角色的能力。

// 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 要求 对模态提交进行交互响应。因此,您需要做出响应。