Skip to content

Commit

Permalink
Add proper command handler (#81)
Browse files Browse the repository at this point in the history
* Initial setup for better command extension

* Finish up initial implementation

* Fix deprecation notice

* Add unit tests
  • Loading branch information
Exanlv authored Feb 28, 2024
1 parent 484807c commit b11ef16
Show file tree
Hide file tree
Showing 11 changed files with 619 additions and 21 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"spatie/regex": "^3.1",
"react/async": "^4.0.0",
"exan/eventer": "^1.0.3",
"exan/reactphp-retrier": "^1.0"
"exan/reactphp-retrier": "^1.0",
"freezemage0/array_find": "^1.0"
},
"require-dev": {
"monolog/monolog": "^3.2",
Expand Down
1 change: 0 additions & 1 deletion fakes/DiscordFake.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ public static function get(): Mock|Discord

$discord->rest = RestFake::get();
$discord->gateway = GatewayFake::get();
$discord->interaction = InteractionHandlerFake::get();

return $discord;
}
Expand Down
89 changes: 89 additions & 0 deletions src/Command/CommandExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

namespace Ragnarok\Fenrir\Command;

use Evenement\EventEmitter;
use Ragnarok\Fenrir\Constants\Events;
use Ragnarok\Fenrir\Discord;
use Ragnarok\Fenrir\Enums\ApplicationCommandOptionType;
use Ragnarok\Fenrir\Enums\InteractionType;
use Ragnarok\Fenrir\Extension\Extension;
use Ragnarok\Fenrir\FilteredEventEmitter;
use Ragnarok\Fenrir\Gateway\Events\InteractionCreate;
use Ragnarok\Fenrir\Interaction\CommandInteraction;
use Ragnarok\Fenrir\Parts\ApplicationCommand;
use Ragnarok\Fenrir\Parts\ApplicationCommandOptionStructure;

use function Freezemage\ArrayUtils\find;

abstract class CommandExtension extends EventEmitter implements Extension
{
protected array $commandMappings = [];

protected FilteredEventEmitter $commandListener;

abstract protected function loadExistingCommands(Discord $discord): void;

public function initialize(Discord $discord): void
{
$this->loadExistingCommands($discord);

$this->registerListener($discord);
}

private function registerListener(Discord $discord): void
{
$this->commandListener = new FilteredEventEmitter(
$discord->gateway->events,
Events::INTERACTION_CREATE,
fn (InteractionCreate $interactionCreate) =>
isset($interactionCreate->type)
&& $interactionCreate->type === InteractionType::APPLICATION_COMMAND
&& isset($this->commandMappings[$interactionCreate->data->id])
);

$this->commandListener->on(Events::INTERACTION_CREATE, function (InteractionCreate $interaction) use ($discord) {
$this->handleInteraction($interaction, $discord);
});

$this->commandListener->start();
}

private function handleInteraction(InteractionCreate $interaction, Discord $discord)
{
$firedCommand = new CommandInteraction($interaction, $discord);

$this->emit($this->commandMappings[$interaction->data->id], [$firedCommand]);
}

protected function getFullNameByCommand(ApplicationCommand $command): string
{
$names = [$command->name];

$this->drillName($command->options ?? [], $names);

return implode('.', $names);
}

private function drillName(array $options, array &$names)
{
/** @var ?ApplicationCommandOptionStructure */
$subCommand = find($options ?? [], function (ApplicationCommandOptionStructure $option) {
return in_array(
$option->type,
[
ApplicationCommandOptionType::SUB_COMMAND,
ApplicationCommandOptionType::SUB_COMMAND_GROUP,
]
);
});

if (!is_null($subCommand)) {
$names[] = $subCommand->name;

$this->drillName($subCommand->options ?? [], $names);
}
}
}
26 changes: 26 additions & 0 deletions src/Command/GlobalCommandExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Ragnarok\Fenrir\Command;

use Ragnarok\Fenrir\Discord;
use Ragnarok\Fenrir\Parts\ApplicationCommand;

class GlobalCommandExtension extends CommandExtension
{
public function __construct(protected readonly string $applicationId)
{
}

protected function loadExistingCommands(Discord $discord): void
{
$discord->rest->globalCommand->getCommands($this->applicationId)
->then(function (array $commands) {
/** @var ApplicationCommand $command */
foreach ($commands as $command) {
$this->commandMappings[$command->id] = $this->getFullNameByCommand($command);
}
});
}
}
26 changes: 26 additions & 0 deletions src/Command/GuildCommandExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Ragnarok\Fenrir\Command;

use Ragnarok\Fenrir\Discord;
use Ragnarok\Fenrir\Parts\ApplicationCommand;

class GuildCommandExtension extends CommandExtension
{
public function __construct(protected readonly string $applicationId, protected readonly string $guildId)
{
}

protected function loadExistingCommands(Discord $discord): void
{
$discord->rest->guildCommand->getCommands($this->guildId, $this->applicationId)
->then(function (array $commands) {
/** @var ApplicationCommand $command */
foreach ($commands as $command) {
$this->commandMappings[$command->id] = $this->getFullNameByCommand($command);
}
});
}
}
24 changes: 24 additions & 0 deletions src/InteractionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ private function handleButtonInteraction(InteractionCreate $interactionCreate):
}
}

/**
* @deprecated
*
* use \Ragnarok\Fenrir\Command\GlobalCommandExtension or \Ragnarok\Fenrir\Command\GuildCommandExtension instead
*
* This implementation is flawed in terms of rate limiting and should not be used.
* It will be removed in a later version
*/
public function registerCommand(CommandBuilder $commandBuilder, callable $handler): void
{
if ($this->devMode) {
Expand All @@ -102,6 +110,14 @@ public function registerCommand(CommandBuilder $commandBuilder, callable $handle
$this->registerGlobalCommand($commandBuilder, $handler);
}

/**
* @deprecated
*
* use \Ragnarok\Fenrir\Command\GlobalCommandExtension or \Ragnarok\Fenrir\Command\GuildCommandExtension instead
*
* This implementation is flawed in terms of rate limiting and should not be used.
* It will be removed in a later version
*/
public function registerGuildCommand(CommandBuilder $commandBuilder, string $guildId, callable $handler): void
{
/** Ready event includes Application ID */
Expand All @@ -119,6 +135,14 @@ function (Ready $ready) use ($guildId, $commandBuilder, $handler) {
);
}

/**
* @deprecated
*
* use \Ragnarok\Fenrir\Command\GlobalCommandExtension or \Ragnarok\Fenrir\Command\GuildCommandExtension instead
*
* This implementation is flawed in terms of rate limiting and should not be used.
* It will be removed in a later version
*/
public function registerGlobalCommand(CommandBuilder $commandBuilder, callable $handler): void
{
/** Ready event includes Application ID */
Expand Down
13 changes: 13 additions & 0 deletions src/Rest/GlobalCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
*/
class GlobalCommand extends HttpResource
{
public function getCommands(string $applicationId, bool $withLocalizations = false): ExtendedPromiseInterface
{
$endpoint = Endpoint::bind(Endpoint::GLOBAL_APPLICATION_COMMANDS, $applicationId);
$endpoint->addQuery('with_localizations', $withLocalizations);

return $this->mapArrayPromise(
$this->http->get(
$endpoint
),
ApplicationCommand::class
);
}

/**
* @see https://discord.com/developers/docs/interactions/application-commands#making-a-global-command
*
Expand Down
13 changes: 13 additions & 0 deletions src/Rest/GuildCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
*/
class GuildCommand extends HttpResource
{
public function getCommands(string $guildId, string $applicationId, bool $withLocalizations = false): ExtendedPromiseInterface
{
$endpoint = Endpoint::bind(Endpoint::GUILD_APPLICATION_COMMANDS, $applicationId, $guildId);
$endpoint->addQuery('with_localizations', $withLocalizations);

return $this->mapArrayPromise(
$this->http->get(
$endpoint
),
ApplicationCommand::class
);
}

/**
* @see https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command
*
Expand Down
19 changes: 0 additions & 19 deletions tests/Command/CommandExtensionTest.php

This file was deleted.

Loading

0 comments on commit b11ef16

Please sign in to comment.