diff --git a/README.md b/README.md index ee52aad..135ef7f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Build functional, elegant bots harnessing the full power of [Laravel](https://la - ⚡️ Out of the box support for databases, caching, and many other Laravel features thanks to [Laravel Zero](https://laravel-zero.com/). - 🚀 Instantly generate working bot [commands](https://laracord.com/docs/commands) and [event listeners](https://laracord.com/docs/events) with 0 knowledge. - 🧑‍💻 Automatic handling of registering/updating/unregistering application [slash commands](https://laracord.com/docs/slash-commands). +- 🚚 Easy to use [interaction routing](https://laracord.com/docs/interactions) for persistence on message buttons and actions. - 👷 Generate asynchronous [services/tasks](https://laracord.com/docs/services) that run parallel to the bot. - 🌎 Optional [HTTP Server](https://laracord.com/docs/http-server) with native Laravel routing and [Livewire support](https://laracord.com/docs/livewire). - 🔧 Fully configurable and extendable. diff --git a/src/Commands/AbstractCommand.php b/src/Commands/AbstractCommand.php index de897d3..c51e6ca 100644 --- a/src/Commands/AbstractCommand.php +++ b/src/Commands/AbstractCommand.php @@ -2,6 +2,7 @@ namespace Laracord\Commands; +use Discord\Parts\Guild\Guild; use Discord\Parts\User\User; use Illuminate\Support\Str; use Laracord\Laracord; @@ -57,6 +58,13 @@ abstract class AbstractCommand */ protected $description; + /** + * The guild the command belongs to. + * + * @var string + */ + protected $guild; + /** * Determines whether the command requires admin permissions. * @@ -227,6 +235,14 @@ public function getDescription() return $this->description; } + /** + * Retrieve the command guild. + */ + public function getGuild(): ?string + { + return $this->guild ?? null; + } + /** * Retrieve the bot instance. * diff --git a/src/Commands/Command.php b/src/Commands/Command.php index 16f8941..0a78ca3 100644 --- a/src/Commands/Command.php +++ b/src/Commands/Command.php @@ -43,14 +43,18 @@ abstract class Command extends AbstractCommand implements CommandContract */ public function maybeHandle($message, $args) { + if ($this->getGuild() && $message->guild_id !== $this->getGuild()) { + return; + } + + $this->server = $message->channel->guild; + if (! $this->isAdminCommand()) { $this->handle($message, $args); return; } - $this->server = $message->channel->guild; - if ($this->isAdminCommand() && ! $this->isAdmin($message->author)) { return; } diff --git a/src/Commands/HelpCommand.php b/src/Commands/HelpCommand.php index 2a00333..957f33a 100644 --- a/src/Commands/HelpCommand.php +++ b/src/Commands/HelpCommand.php @@ -48,7 +48,10 @@ class HelpCommand extends Command */ public function handle($message, $args) { - $commands = collect($this->bot()->getRegisteredCommands())->filter(fn ($command) => ! $command->isHidden()); + $commands = collect($this->bot()->getRegisteredCommands()) + ->filter(fn ($command) => ! $command->isHidden()) + ->filter(fn ($command) => $command->getGuild() ? $message->guild_id === $command->getGuild() : true) + ->sortBy('name'); $fields = []; diff --git a/src/Commands/SlashCommand.php b/src/Commands/SlashCommand.php index 75e8a9b..ed9a814 100644 --- a/src/Commands/SlashCommand.php +++ b/src/Commands/SlashCommand.php @@ -13,13 +13,6 @@ abstract class SlashCommand extends AbstractCommand implements SlashCommandContract { - /** - * The guild the command belongs to. - * - * @var string - */ - protected $guild; - /** * The permissions required to use the command. * @@ -91,6 +84,8 @@ abstract public function handle($interaction); */ public function maybeHandle($interaction) { + $this->server = $interaction->guild; + if (! $this->isAdminCommand()) { $this->parseOptions($interaction); @@ -101,8 +96,6 @@ public function maybeHandle($interaction) return; } - $this->server = $interaction->guild; - if ($this->isAdminCommand() && ! $this->isAdmin($interaction->member->user)) { return $interaction->respondWithMessage( $this @@ -192,14 +185,6 @@ public function getSignature() return Str::start($this->getName(), '/'); } - /** - * Retrieve the slash command guild. - */ - public function getGuild(): ?string - { - return $this->guild ?? null; - } - /** * Retrieve the slash command bitwise permission. */ diff --git a/src/Discord/Message.php b/src/Discord/Message.php index d5d3d02..866667f 100644 --- a/src/Discord/Message.php +++ b/src/Discord/Message.php @@ -528,10 +528,12 @@ public function color(string $color): self }; if (str_starts_with($color, '#')) { - $color = hexdec(substr($color, 1)); + $color = hexdec( + Str::of($color)->replace('#', '')->limit(6, '')->toString() + ); } - $this->color = $color; + $this->color = (string) $color; return $this; } diff --git a/src/Events/Event.php b/src/Events/Event.php index db94917..2fda648 100644 --- a/src/Events/Event.php +++ b/src/Events/Event.php @@ -146,7 +146,7 @@ public static function getEvents(): array } $classes = collect(File::allFiles($source)) - ->mapWithKeys(fn ($file) => [Str::of($file->getFilename())->replace('.php', '')->__toString() => $file->getPathname()]); + ->mapWithKeys(fn ($file) => [Str::of($file->getFilename())->replace('.php', '')->toString() => $file->getPathname()]); return collect($events)->mapWithKeys(function ($path, $event) use ($classes) { $class = Str::of($event) @@ -154,13 +154,17 @@ public static function getEvents(): array ->replace('_', ' ') ->headline() ->replace(' ', '') - ->__toString(); + ->toString(); if (! $classes->has($class)) { return []; } - $name = Str::of($event)->lower()->replace('_', ' ')->headline()->__toString(); + $name = Str::of($event) + ->lower() + ->replace('_', ' ') + ->headline() + ->toString(); return [$event => [ 'key' => $event, diff --git a/src/Laracord.php b/src/Laracord.php index 2fbc1e6..2f2a9ab 100644 --- a/src/Laracord.php +++ b/src/Laracord.php @@ -37,7 +37,7 @@ class Laracord /** * The event loop. */ - protected $loop; + protected ?LoopInterface $loop = null; /** * The application instance. @@ -141,6 +141,13 @@ class Laracord */ protected $httpServer; + /** + * The logger instance. + * + * @var \Laracord\Logging\Logger + */ + protected $logger; + /** * The registered bot commands. */ @@ -208,18 +215,10 @@ public function boot(): void $this->afterBoot(); $this->getLoop()->addTimer(1, function () { - $status = collect([ - 'command' => count($this->registeredCommands), - 'event' => count($this->registeredEvents), - 'service' => count($this->registeredServices), - 'routes' => count(Route::getRoutes()->getRoutes()), - ]) - ->filter() - ->map(function ($count, $type) { - $string = Str::plural($type, $count); - - return "{$count} {$string}"; - })->implode(', '); + $status = $this + ->getStatus() + ->map(fn ($count, $type) => "{$count} {$type}") + ->implode(', '); $status = Str::replaceLast(', ', ', and ', $status); @@ -289,11 +288,13 @@ protected function handleStream(string $data): void 'restart' => $this->restart(), 'invite' => $this->showInvite(force: true), 'commands' => $this->showCommands(), + 'status' => $this->showStatus(), '?' => $this->console()->table(['Command', 'Description'], [ ['shutdown', 'Shutdown the bot.'], ['restart', 'Restart the bot.'], ['invite', 'Show the invite link.'], ['commands', 'Show the registered commands.'], + ['status', 'Show the bot status.'], ]), default => $this->console()->error("Unknown command: {$command}"), }; @@ -757,6 +758,27 @@ public function showCommands(): self return $this; } + /** + * Print the bot status to console. + */ + public function showStatus(): self + { + $uptime = now()->createFromTimestamp(LARAVEL_START)->diffForHumans(); + + $status = $this->getStatus() + ->prepend($this->discord()->users->count(), 'user') + ->prepend($this->discord()->guilds->count(), 'guild') + ->mapWithKeys(fn ($count, $type) => [Str::plural($type, $count) => $count]) + ->prepend($uptime, 'uptime') + ->prepend("{$this->discord()->username} ({$this->discord()->id})", 'bot') + ->map(fn ($count, $type) => Str::of($type)->title()->finish(": {$count}")->toString()); + + $this->console()->line(' Status'); + $this->console()->outputComponents()->bulletList($status->all()); + + return $this; + } + /** * Show the invite link if the bot is not in any guilds. */ @@ -848,7 +870,7 @@ public function getOptions(): array $defaultOptions = [ 'intents' => $this->getIntents(), - 'logger' => Logger::make($this->console), + 'logger' => $this->getLogger(), 'loop' => $this->getLoop(), ]; @@ -858,6 +880,14 @@ public function getOptions(): array ]; } + /** + * Get the logger instance. + */ + public function getLogger(): Logger + { + return $this->logger ??= Logger::make($this->console); + } + /** * Get the Discord admins. */ @@ -1101,6 +1131,20 @@ public function getApplication(): Application return $this->app; } + /** + * Retrieve the bot status collection. + */ + public function getStatus(): Collection + { + return collect([ + 'command' => count($this->registeredCommands), + 'event' => count($this->registeredEvents), + 'service' => count($this->registeredServices), + 'interaction' => count($this->interactions), + 'route' => count(Route::getRoutes()->getRoutes()), + ])->filter()->mapWithKeys(fn ($count, $type) => [Str::plural($type, $count) => $count]); + } + /** * Safely handle the provided callback. */