plytas/laravel-discord-interactions

使用 Discord HTTP API 创建和响应交互的 Laravel (PHP) 客户端

0.6 2024-08-15 17:07 UTC

This package is auto-updated.

Last update: 2024-09-15 17:24:07 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

使用 Laravel 创建丰富的 Discord 交互并对其做出响应。利用 Discord HTTP webhooks,无需长时间运行进程。

安装

您可以通过 composer 安装此包

composer require plytas/laravel-discord-interactions

您可以使用以下命令发布配置文件:

php artisan vendor:publish --tag="discord-interactions-config"

这是发布配置文件的内容

return [
    'application_id' => env('DISCORD_APPLICATION_ID'),
    'public_key' => env('DISCORD_PUBLIC_KEY'),
    'bot_token' => env('DISCORD_BOT_TOKEN'),

    'route' => [
        'path' => env('DISCORD_ROUTE', '/discord'),

        'middleware' => [
            'before' => [
                //ThrottleRequests::class,
            ],
            'after' => [

            ],
        ],
    ],
];

配置

https://discord.com/developers/applications 上创建一个新的 Discord 应用程序。从“通用信息”选项卡复制应用程序 ID 和公钥,从“机器人”选项卡复制机器人令牌,并将其添加到您的 .env 文件中。

交互端点设置

对于本地开发,您可以使用例如 exposengrok 这样的服务将您的本地服务器公开到互联网。

在您的 Discord 应用程序中,在“通用信息”下填写“交互端点 URL”,使用您服务器的 URL,后跟配置文件中设置的路径。默认情况下为 /discord

https://your-server-url.com/discord

点击“保存更改”按钮时,Discord 将向提供的 URL 发送几个 POST 请求以验证端点。其中之一将故意包含无效签名。该包将自动对这些请求做出适当的响应。

如果更改成功保存,您就可以开始使用了!

用法

创建命令

要创建新的聊天命令,创建一个新的类,该类实现了 Plytas\Discord\Contracts\DiscordCommand 接口。

use Plytas\Discord\Contracts\DiscordCommand;
use Plytas\Discord\Data\DiscordInteraction;
use Plytas\Discord\Data\DiscordMessage;
use Plytas\Discord\Data\DiscordResponse;

class PingCommand implements DiscordCommand
{
    public function description(): string
    {
        return 'Replies with pong';
    }

    public function handle(DiscordInteraction $interaction): DiscordResponse
    {
        return $interaction->respondWithMessage(
            DiscordMessage::new()
                ->setContent('Pong!')
                ->ephemeral()
        );
    }
}

注册命令

您可以使用 Plytas\Discord\DiscordCommandRegistry::setCommands 方法注册命令。这可以在服务提供者的 boot() 方法中完成。

use Plytas\Discord\DiscordCommandRegistry;

DiscordCommandRegistry::setCommands([
    'ping' => PingCommand::class,
    
    // Nested commands
    'user' => [
        'role' => [
            'add' => AddRoleCommand::class,
            'remove' => RemoveRoleCommand::class,
        ],
    ],
]);

最后调用 php artisan discord:register-commands 以将命令注册到 Discord。建议在部署脚本中运行此命令。

响应交互

您的命令将接收一个包含有关交互信息的 DiscordInteraction 对象。您可以通过直接返回一个 DiscordResponse 对象来直接响应交互。DiscordInteraction 对象包含创建响应的辅助方法,例如 respondWithMessage()updateMessage()showModal()

您必须在 3 秒内对交互做出响应。如果您未能这样做,Discord 将向用户显示一个通用错误消息。如果您需要更多时间来处理交互,建议您返回一条消息,表明命令仍在处理中。完成后,您可以在 \Illuminate\Support\Facades\App::terminating() 回调中使用 \Plytas\Discord\Facades\Discord::updateInteractionMessage() 来更新消息。

use Illuminate\Support\Facades\App;
use Plytas\Discord\Contracts\DiscordCommand;
use Plytas\Discord\Data\DiscordInteraction;
use Plytas\Discord\Data\DiscordMessage;
use Plytas\Discord\Data\DiscordResponse;
use Plytas\Discord\Facades\Discord;

class PingCommand implements DiscordCommand
{
    public function description(): string
    {
        return 'Replies with pong';
    }

    public function handle(DiscordInteraction $interaction): DiscordResponse
    {
        // Register a terminating callback to update the message after processing is done
        App::terminating(function () use ($interaction) {
            // Simulate a long-running process
            sleep(5);
        
            Discord::updateInteractionMessage(
                interaction: $interaction,
                message: DiscordMessage::new()
                    ->setContent('Pong!')
                    ->ephemeral()
            );
        });
    
        // Respond with a message that indicates that the command is still processing
        return $interaction->respondWithMessage(
            DiscordMessage::new()
                ->setContent('Calculating...')
                ->ephemeral() // Only the user who invoked the command can see the message
        );
    }
}

响应组件交互

您可以在消息中添加组件,以便用户与之交互。

use Plytas\Discord\Components\ActionRow;
use Plytas\Discord\Components\Button;
use Plytas\Discord\Contracts\DiscordCommand;
use Plytas\Discord\Data\DiscordInteraction;
use Plytas\Discord\Data\DiscordMessage;
use Plytas\Discord\Data\DiscordResponse;
use Plytas\Discord\Enums\ButtonStyle;

class RockPaperScissorsCommand implements DiscordCommand
{
    public function description(): string
    {
        return 'Play rock-paper-scissors';
    }

    public function handle(DiscordInteraction $interaction): DiscordResponse
    {
        return $interaction->respondWithMessage(
            DiscordMessage::new()
                ->setContent('Select your move')
                ->addComponent(
                    component: ActionRow::new()
                        ->addComponent(
                            component: Button::new()
                                ->setCustomId('rock')
                                ->setLabel('Rock')
                                ->setEmoji('🪨')
                                ->setStyle(ButtonStyle::Primary)
                        )
                        ->addComponent(
                            component: Button::new()
                                ->setCustomId('paper')
                                ->setLabel('Paper')
                                ->setEmoji('📄')
                                ->setStyle(ButtonStyle::Primary)
                        )
                        ->addComponent(
                            component: Button::new()
                                ->setCustomId('scissors')
                                ->setLabel('Scissors')
                                ->setEmoji('✂️')
                                ->setStyle(ButtonStyle::Primary)
                        )
                )
                ->ephemeral() // Only the user who invoked the command can see the message
        );
    }
}

您可以通过创建一个新的类并实现 \Plytas\Discord\Contracts\DiscordComponentHandler 接口来响应组件交互。

use Illuminate\Support\Arr;
use Plytas\Discord\Contracts\DiscordComponentHandler;
use Plytas\Discord\Data\DiscordInteraction;
use Plytas\Discord\Data\DiscordMessage;
use Plytas\Discord\Data\DiscordResponse;

class RockPaperScissorsComponentHandler implements DiscordComponentHandler
{
    public function handle(DiscordInteraction $interaction): DiscordResponse
    {
        $playerSelection = $interaction->getMessageComponent()->custom_id;
        $botSelection = Arr::random(['rock', 'paper', 'scissors']);
        
        if ($playerSelection === $botSelection) {
            $message = 'It\'s a tie!';
        }
        
        $winningMoves = [
            'rock' => 'scissors',
            'paper' => 'rock',
            'scissors' => 'paper',
        ];
        
        if ($winningMoves[$playerSelection] === $botSelection) {
            $message = 'You win!';
        } else {
            $message = 'You lose!';
        }
        
        return $interaction->updateMessage(
            DiscordMessage::new()
                ->setContent($message)
                ->ephemeral()
        );
    }
}

注册组件处理器

您可以使用 Plytas\Discord\DiscordComponentRegistry::setComponentHandlers() 方法注册组件处理器。此方法接受一个 component_id => DiscordComponentHandler class 的数组。这可以在服务提供者的 boot() 方法中完成。

use Plytas\Discord\DiscordComponentRegistry;

DiscordComponentRegistry::setComponentHandlers([
    'rock' => RockPaperScissorsComponentHandler::class,
    'paper' => RockPaperScissorsComponentHandler::class,
    'scissors' => RockPaperScissorsComponentHandler::class,
]);

如果您需要更多控制组件处理器注册过程,可以使用 Plytas\Discord\DiscordComponentRegistry::handleComponentsUsing() 方法。此方法接受一个闭包,该闭包接收 Plytas\Discord\Data\DiscordInteraction 对象,并期望返回一个 Plytas\Discord\Data\DiscordResponse 对象。

想象一下,您的组件具有一个动态的自定义ID,其中包含游戏名称。您可以使用以下代码来处理这些组件

use Illuminate\Support\Str;
use Plytas\Discord\Data\DiscordInteraction;
use Plytas\Discord\Events\ComponentHandlerRegistered;

DiscordComponentRegistry::handleComponentsUsing(function(DiscordInteraction $interaction)  {
    $gameName = Str::afterLast($interaction->getMessageComponent()->custom_id, ':');
    
    return match ($gameName) {
        'rock-paper-scissors' => (new RockPaperScissorsComponentHandler)->handle($interaction),
        'coin-flip' => (new CoinFlipComponentHandler)->handle($interaction),
        'default' => $interaction->updateMessage(
            DiscordMessage::new()
                ->setContent('Invalid game')
                ->ephemeral()
        );
    };
});

警告

并非所有组件和API功能都得到支持。此包最初是一个个人项目,仍在开发中。如果您需要特定的功能,请随时提出问题或发起拉取请求。

测试

composer test

变更日志

有关最近更改的更多信息,请参阅变更日志

贡献

有关详细信息,请参阅贡献指南

安全漏洞

有关如何报告安全漏洞的详细信息,请参阅我们的安全策略

致谢

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可证文件