diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..daa51f2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +quote_type = single + +[*.md] +trim_trailing_whitespace = false + +[*.php] +indent_size = 4 + +[*.blade.php] +indent_size = 2 + +[resources/views/**.php] +indent_size = 2 + +[index.php] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6daa0e7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text=auto eol=lf +/.github export-ignore +.scrutinizer.yml export-ignore +BACKERS.md export-ignore +CONTRIBUTING.md export-ignore +CHANGELOG.md export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b7ef35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor +composer.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..36301bc --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "es5" +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c057b8f --- /dev/null +++ b/composer.json @@ -0,0 +1,41 @@ +{ + "name": "laracord/framework", + "type": "package", + "description": "The Laracord Framework.", + "keywords": ["framework", "laravel", "discord"], + "license": "MIT", + "homepage": "https://laracord.com", + "authors": [ + { + "name": "Brandon Nifong", + "email": "brandon@tendency.me", + "homepage": "https://github.com/log1x" + } + ], + "require": { + "php": "^8.1", + "guzzlehttp/guzzle": "^7.5", + "illuminate/database": "^10.0", + "illuminate/http": "^10.0", + "laravel-zero/framework": "^10.2", + "nunomaduro/termwind": "^1.15.1", + "openai-php/client": "^0.8.0", + "react/child-process": "^0.6.5", + "react/event-loop": "^1.5", + "team-reflex/discord-php": "^7.3" + }, + "require-dev": { + "laravel/pint": "^1.13" + }, + "autoload": { + "psr-4": { + "Laracord\\": "src/" + } + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "php-http/discovery": true + } + } +} diff --git a/config/commands.php b/config/commands.php new file mode 100644 index 0000000..b286e24 --- /dev/null +++ b/config/commands.php @@ -0,0 +1,85 @@ + Laracord\Console\Commands\BootCommand::class, + + /* + |-------------------------------------------------------------------------- + | Commands Paths + |-------------------------------------------------------------------------- + | + | This value determines the "paths" that should be loaded by the console's + | kernel. Foreach "path" present on the array provided below the kernel + | will extract all "Illuminate\Console\Command" based class commands. + | + */ + + 'paths' => [ + app_path('Console/Commands'), + ], + + /* + |-------------------------------------------------------------------------- + | Added Commands + |-------------------------------------------------------------------------- + | + | You may want to include a single command class without having to load an + | entire folder. Here you can specify which commands should be added to + | your list of commands. The console's kernel will try to load them. + | + */ + + 'add' => [ + // .. + ], + + /* + |-------------------------------------------------------------------------- + | Hidden Commands + |-------------------------------------------------------------------------- + | + | Your application commands will always be visible on the application list + | of commands. But you can still make them "hidden" specifying an array + | of commands below. All "hidden" commands can still be run/executed. + | + */ + + 'hidden' => [ + NunoMaduro\LaravelConsoleSummary\SummaryCommand::class, + Symfony\Component\Console\Command\DumpCompletionCommand::class, + Symfony\Component\Console\Command\HelpCommand::class, + Illuminate\Console\Scheduling\ScheduleRunCommand::class, + Illuminate\Console\Scheduling\ScheduleListCommand::class, + Illuminate\Console\Scheduling\ScheduleFinishCommand::class, + Illuminate\Foundation\Console\VendorPublishCommand::class, + LaravelZero\Framework\Commands\StubPublishCommand::class, + ], + + /* + |-------------------------------------------------------------------------- + | Removed Commands + |-------------------------------------------------------------------------- + | + | Do you have a service provider that loads a list of commands that + | you don't need? No problem. Laravel Zero allows you to specify + | below a list of commands that you don't to see in your app. + | + */ + + 'remove' => [ + LaravelZero\Framework\Commands\MakeCommand::class, + ], + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..d088a3f --- /dev/null +++ b/config/database.php @@ -0,0 +1,101 @@ + env('DB_CONNECTION', 'sqlite'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DATABASE_URL'), + 'database' => config('app.env') === 'production' ? + getcwd().'/database/database.sqlite' : + env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run in the database. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), + ], + + ], + +]; diff --git a/config/discord.php b/config/discord.php new file mode 100644 index 0000000..ebc75f5 --- /dev/null +++ b/config/discord.php @@ -0,0 +1,57 @@ + env('DISCORD_BOT_DESCRIPTION', 'The Laracord Discord Bot.'), + + /* + |-------------------------------------------------------------------------- + | Discord Token + |-------------------------------------------------------------------------- + | + | Here you may specify your Discord bot token. You can find it under the + | "Bot" section of your Discord application. Make sure to keep this + | token private and never share it with anyone for security. + | + */ + + 'token' => env('DISCORD_TOKEN', ''), + + /* + |-------------------------------------------------------------------------- + | Command Prefix + |-------------------------------------------------------------------------- + | + | Here you may specify the command prefix for the Discord bot. This + | prefix will be used to distinguish commands from regular chat + | messages. You may change this to anything you like. + | + */ + + 'prefix' => env('DISCORD_COMMAND_PREFIX', '!'), + + /* + |-------------------------------------------------------------------------- + | Help Command + |-------------------------------------------------------------------------- + | + | Here you may specify whether the help command should be enabled. This + | command will display a list of all available commands. You may + | change this to `false` if you don't want to use this. + | + */ + + 'help' => true, + +]; diff --git a/src/Commands/Command.php b/src/Commands/Command.php new file mode 100644 index 0000000..2fb8eea --- /dev/null +++ b/src/Commands/Command.php @@ -0,0 +1,375 @@ + 3066993, + 'success' => 3066993, + 'error' => 15158332, + 'warning' => 15105570, + 'info' => 3447003, + ]; + + /** + * Create a new console command instance. + * + * @return void + */ + public function __construct(Laracord $bot) + { + $this->bot = $bot; + $this->console = $bot->getConsole(); + $this->discord = $bot->getDiscord(); + } + + /** + * Maybe handle the Discord command. + * + * @param \Discord\Parts\Channel\Message $message + * @param array $args + * @return mixed + */ + public function maybeHandle($message, $args) + { + $this->user = User::firstOrCreate(['discord_id' => $message->author->id], [ + 'discord_id' => $message->author->id, + 'username' => $message->author->username, + 'is_admin' => in_array($message->author->username, config('discord.admins', [])), + ]); + + $this->server = $message->channel->guild; + + if ($this->isAdminCommand() && ! $this->user->is_admin) { + return; + } + + $this->handle($message, $args); + } + + /** + * Handle the Discord command. + * + * @param \Discord\Parts\Channel\Message $message + * @param array $args + * @return mixed + */ + abstract public function handle($message, $args); + + /** + * Build an embed for use in a Discord message. + * + * @param \Discord\Parts\Channel\Message $message + * @param string $title + * @param string $content + * @param array $fields + * @param bool $inline + * @param string $color + * @return array + */ + public function message($message, $title, $content = '', $fields = [], $inline = true, $color = 'default') + { + if (is_array($content)) { + $fields = $content; + } + + if (! empty($fields)) { + $fields = array_map(function ($key, $value) use ($inline) { + return [ + 'name' => $key, + 'value' => $value, + 'inline' => $inline, + ]; + }, array_keys($fields), array_values($fields)); + } + + return $message->sendMessage('', false, [ + 'author' => [ + 'name' => $this->discord->user->username, + 'icon_url' => $this->discord->user->avatar, + ], + 'color' => $this->embedColors[$color] ?? $this->embedColors['default'], + 'title' => $title, + 'description' => ! is_array($content) ? $content : '', + 'fields' => $fields, + ]); + } + + /** + * Send a log to console. + * + * @param string $message + * @return void + */ + public function log($message) + { + return $this->console->info($message); + } + + /** + * Get the command user. + * + * @param \Discord\Parts\User\User|null $user + * @return \App\Models\User + */ + public function getUser($user = null) + { + return $user ? User::firstOrCreate(['discord_id' => $user->id], [ + 'discord_id' => $user->id, + 'username' => $user->username, + 'is_admin' => in_array($user->username, config('discord.admins', [])), + ]) : $this->user; + } + + /** + * Get the command server. + * + * @return \Discord\Parts\Guild\Guild + */ + public function getServer() + { + return $this->server; + } + + /** + * Resolve a Discord user. + * + * @param string $username + * @return \App\Models\User|null + */ + public function resolveUser($username = null) + { + return ! empty($username) ? $this->getServer()->members->filter(function ($member) use ($username) { + $username = str_replace(['<', '@', '>'], '', strtolower($username)); + + return ($member->user->username === $username || $member->user->id === $username) && ! $member->user->bot; + })->first() : null; + } + + /** + * Retrieve the command name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Retrieve the full command syntax. + * + * @return string + */ + public function getSyntax() + { + $command = "{$this->getBot()->getPrefix()}{$this->name}"; + + if (! empty($this->usage)) { + $command .= " `{$this->usage}`"; + } + + return $command; + } + + /** + * Retrieve the command aliases. + * + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Retrieve the command description. + * + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Retrieve the command cooldown. + * + * @return int + */ + public function getCooldown() + { + return $this->cooldown; + } + + /** + * Retrieve the command cooldown message. + * + * @return string + */ + public function getCooldownMessage() + { + return $this->cooldownMessage; + } + + /** + * Retrieve the command usage. + * + * @return string + */ + public function getUsage() + { + return $this->usage; + } + + /** + * Retrieve the bot instance. + * + * @return \App\Bot\Bot + */ + public function getBot() + { + return $this->bot; + } + + /** + * Retrieve the console instance. + * + * @return \LaravelZero\Framework\Commands\Command + */ + public function getConsole() + { + return $this->console; + } + + /** + * Retrieve the Discord instance. + * + * @return \Discord\DiscordCommandClient + */ + public function getDiscord() + { + return $this->discord; + } + + /** + * Determine if the command requires admin permissions. + * + * @return bool + */ + public function isAdminCommand() + { + return $this->admin; + } + + /** + * Determine if the command is hidden. + * + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } +} diff --git a/src/Commands/Contracts/Command.php b/src/Commands/Contracts/Command.php new file mode 100644 index 0000000..3e8fced --- /dev/null +++ b/src/Commands/Contracts/Command.php @@ -0,0 +1,15 @@ +getBot()->getCommands())->filter(fn ($command) => ! $command->isHidden()); + + $fields = []; + + foreach ($commands as $command) { + $fields[$command->getSyntax()] = $command->getDescription(); + } + + if (count($fields) % 3 !== 0) { + $fields[' '] = ''; + } + + if (count($fields) % 3 !== 0) { + $fields[' '] = ''; + } + + return $this->message( + $message->channel, + $this->title, + $this->message, + $fields + ); + } +} diff --git a/src/Console/Commands/BootCommand.php b/src/Console/Commands/BootCommand.php new file mode 100644 index 0000000..7b66b4c --- /dev/null +++ b/src/Console/Commands/BootCommand.php @@ -0,0 +1,32 @@ +boot(); + } +} diff --git a/src/Console/Commands/ConsoleMakeCommand.php b/src/Console/Commands/ConsoleMakeCommand.php new file mode 100644 index 0000000..1908db4 --- /dev/null +++ b/src/Console/Commands/ConsoleMakeCommand.php @@ -0,0 +1,44 @@ +laravel->basePath(trim($relativePath, '/'))) + ? $customPath + : __DIR__.$relativePath; + } + + /** + * {@inheritdoc} + */ + protected function getDefaultNamespace($rootNamespace): string + { + return $rootNamespace.'\Console\Commands'; + } +} diff --git a/src/Console/Commands/MakeCommand.php b/src/Console/Commands/MakeCommand.php new file mode 100644 index 0000000..27fab05 --- /dev/null +++ b/src/Console/Commands/MakeCommand.php @@ -0,0 +1,100 @@ +option('command') ?: 'bot:'.Str::of($name)->classBasename()->kebab()->value(); + + return str_replace(['dummy:command', '{{ command }}'], $command, $stub); + } + + /** + * Get the stub file for the generator. + * + * @return string + */ + protected function getStub() + { + $relativePath = '/stubs/command.stub'; + + return file_exists($customPath = $this->laravel->basePath(trim($relativePath, '/'))) + ? $customPath + : __DIR__.$relativePath; + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace.'\Commands'; + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['name', InputArgument::REQUIRED, 'The name of the command'], + ]; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the console command already exists'], + ['command', null, InputOption::VALUE_OPTIONAL, 'The terminal command that will be used to invoke the class'], + ]; + } +} diff --git a/src/Console/Commands/stubs/command.stub b/src/Console/Commands/stubs/command.stub new file mode 100644 index 0000000..6b63fc3 --- /dev/null +++ b/src/Console/Commands/stubs/command.stub @@ -0,0 +1,53 @@ +message( + $message->channel, + 'Ping', + 'Pong!', + ['Response time' => (string) $message->timestamp->diff()->f] + ); + } +} diff --git a/src/Console/Commands/stubs/console.stub b/src/Console/Commands/stubs/console.stub new file mode 100644 index 0000000..7860202 --- /dev/null +++ b/src/Console/Commands/stubs/console.stub @@ -0,0 +1,44 @@ +command(static::class)->everyMinute(); + } +} diff --git a/src/Discord/Embed.php b/src/Discord/Embed.php new file mode 100644 index 0000000..cf01cf8 --- /dev/null +++ b/src/Discord/Embed.php @@ -0,0 +1,497 @@ +token = config('discord.token'); + } + + /** + * Make a new Discord embed instance. + * + * @return static + */ + public static function make(): self + { + return new static(); + } + + /** + * Get the Discord HTTP client. + */ + protected function client(): PendingRequest + { + return Http::withHeaders([ + 'Authorization' => "Bot {$this->getToken()}", + ])->baseUrl($this->getEndpoint()); + } + + /** + * Send the message to Discord. + * + * @return \Illuminate\Http\Client\Response + */ + public function send() + { + $response = $this->client()->post('messages', $this->toArray()); + + return $response; + } + + /** + * Get the Discord message endpoint. + */ + protected function getEndpoint(): string + { + return str_replace('{channel_id}', $this->getChannel(), $this->endpoint); + } + + /** + * Get the Discord channel. + */ + protected function getChannel(): string + { + if (! $this->channel) { + throw new Exception('You must specify a Discord channel.'); + } + + return $this->channel; + } + + /** + * Get the Discord token. + */ + protected function getToken(): string + { + return $this->token; + } + + /** + * Get the components. + */ + public function getComponents(): array + { + if (empty($this->components)) { + return []; + } + + return [ + 'type' => 1, + 'components' => $this->components, + ]; + } + + /** + * Set the channel. + * + * @return $this + */ + public function channel(string $channel): self + { + $this->channel = $channel; + + return $this; + } + + /** + * Set the message username. + */ + public function username(?string $username): self + { + $this->username = $username; + + return $this; + } + + /** + * Set the message content. + */ + public function content(?string $content): self + { + $this->content = $content; + + return $this; + } + + /** + * Set the message avatar URL. + */ + public function avatarUrl(?string $avatarUrl): self + { + $this->avatarUrl = $avatarUrl; + + return $this; + } + + /** + * Set whether the message should be text-to-speech. + */ + public function tts(bool $tts): self + { + $this->tts = $tts; + + return $this; + } + + /** + * Set the message title. + */ + public function title(?string $title): self + { + $this->title = $title; + + return $this; + } + + /** + * Set the message description. + */ + public function description(?string $description): self + { + $this->description = $description; + + return $this; + } + + /** + * Set the message color. + */ + public function color(?string $color): self + { + $this->color = $color; + + return $this; + } + + /** + * Set the message footer icon. + */ + public function footerIcon(?string $footerIcon): self + { + $this->footerIcon = $footerIcon; + + return $this; + } + + /** + * Set the message footer text. + */ + public function footerText(?string $footerText): self + { + $this->footerText = $footerText; + + return $this; + } + + /** + * Set the message thumbnail URL. + */ + public function thumbnailUrl(?string $thumbnailUrl): self + { + $this->thumbnailUrl = $thumbnailUrl; + + return $this; + } + + /** + * Set the message URL. + */ + public function url(?string $url): self + { + $this->url = $url; + + return $this; + } + + /** + * Set the message image URL. + */ + public function imageUrl(?string $imageUrl): self + { + $this->imageUrl = $imageUrl; + + return $this; + } + + /** + * Set the message timestamp. + */ + public function timestamp(?string $timestamp): self + { + $this->timestamp = $timestamp; + + return $this; + } + + /** + * Set the message author name. + */ + public function authorName(?string $authorName): self + { + $this->authorName = $authorName; + + return $this; + } + + /** + * Set the message author URL. + */ + public function authorUrl(?string $authorUrl): self + { + $this->authorUrl = $authorUrl; + + return $this; + } + + /** + * Set the message author icon. + */ + public function authorIcon(?string $authorIcon): self + { + $this->authorIcon = $authorIcon; + + return $this; + } + + /** + * Set the message fields. + */ + public function fields(array $fields): self + { + $this->fields = $fields; + + return $this; + } + + /** + * Add a field to the message. + */ + public function field(string $name, mixed $value, bool $inline = true, bool $condition = false): self + { + if ($condition) { + return $this; + } + + $this->fields[] = [ + 'name' => $name, + 'value' => "{$value}", + 'inline' => $inline, + ]; + + return $this; + } + + /** + * Add a code field to the message. + */ + public function codeField(string $name, string $value, string $language = 'py', bool $condition = false): self + { + if ($condition) { + return $this; + } + + return $this->field($name, "```{$language}\n{$value}\n```", false); + } + + /** + * Set the message components. + */ + public function components(array $components): self + { + $this->components = $components; + + return $this; + } + + /** + * Add a URL button to the message. + */ + public function button(string $label, string $url, mixed $emoji = null): self + { + $button = [ + 'type' => 2, + 'label' => $label, + 'style' => 5, + 'url' => $url, + ]; + + if ($emoji) { + $emoji = is_array($emoji) ? array_merge([ + 'id' => null, + 'animated' => false, + ], $emoji) : [ + 'name' => $emoji, + 'id' => null, + 'animated' => false, + ]; + + $button['emoji'] = $emoji; + } + + $this->components[] = $button; + + return $this; + } + + /** + * Convert the message to JSON. + */ + public function toJson(): string + { + return json_encode($this->toArray()); + } + + /** + * Convert the message to an array. + */ + public function toArray(): array + { + return [ + 'content' => $this->content, + 'tts' => $this->tts, + 'components' => [$this->getComponents()], + 'embeds' => [ + [ + 'title' => $this->title, + 'description' => $this->description, + 'url' => $this->url, + 'timestamp' => $this->timestamp, + 'color' => $this->color, + 'footer' => [ + 'text' => $this->footerText, + 'icon_url' => $this->footerIcon, + ], + 'thumbnail' => [ + 'url' => $this->thumbnailUrl, + ], + 'image' => [ + 'url' => $this->imageUrl, + ], + 'author' => [ + 'name' => $this->authorName, + 'url' => $this->authorUrl, + 'icon_url' => $this->authorIcon, + ], + 'fields' => $this->fields, + ], + ], + ]; + } +} diff --git a/src/Laracord.php b/src/Laracord.php new file mode 100644 index 0000000..e058eb4 --- /dev/null +++ b/src/Laracord.php @@ -0,0 +1,159 @@ +console = $console; + + $this->name = config('app.name'); + $this->description = config('discord.description'); + $this->token = config('discord.token'); + $this->prefix = config('discord.prefix'); + } + + /** + * Make the Bot instance. + * + * @return $this + */ + public static function make(LaravelCommand $console): self + { + return new static($console); + } + + /** + * Boot the bot. + */ + public function boot(): void + { + $this->discord = new Discord([ + 'token' => $this->token, + 'prefix' => $this->prefix, + 'description' => $this->description, + 'defaultHelpCommand' => false, + 'discordOptions' => [ + 'intents' => Intents::getDefaultIntents() | Intents::GUILD_MEMBERS, + 'logger' => Logger::make($this->console), + 'loadAllMembers' => true, + ], + ]); + + if (config('discord.help')) { + $this->commands[] = Commands\HelpCommand::class; + } + + foreach ($this->commands as $command) { + $command = new $command($this); + + $this->discord->registerCommand($command->getName(), fn ($message, $args) => $command->maybeHandle($message, $args), [ + 'cooldown' => $command->getCooldown() ?: 0, + 'cooldownMessage' => $command->getCooldownMessage() ?: '', + 'description' => $command->getDescription() ?: '', + 'usage' => $command->getUsage() ?: '', + 'aliases' => $command->getAliases(), + ]); + + $this->registeredCommands[] = $command; + } + + $commands = count($this->registeredCommands); + $commands = $commands === 1 + ? "{$commands} command" + : "{$commands} commands"; + + $this->console->outputComponents()->info("Booting {$this->name} with {$commands}"); + + $this->discord->run(); + } + + /** + * Get the command list. + */ + public function getCommands(): array + { + return $this->registeredCommands; + } + + /** + * Get the Discord instance. + * + * @return \Discord\Discord + */ + public function getDiscord(): Discord + { + return $this->discord; + } + + /** + * Get the console instance. + */ + public function getConsole(): LaravelCommand + { + return $this->console; + } + + /** + * Retrieve the prefix. + */ + public function getPrefix(): string + { + return $this->prefix; + } +} diff --git a/src/LaracordServiceProvider.php b/src/LaracordServiceProvider.php new file mode 100644 index 0000000..b4f1cf2 --- /dev/null +++ b/src/LaracordServiceProvider.php @@ -0,0 +1,34 @@ +mergeConfigFrom(__DIR__.'/../config/discord.php', 'discord'); + $this->mergeConfigFrom(__DIR__.'/../config/commands.php', 'commands'); + $this->mergeConfigFrom(__DIR__.'/../config/database.php', 'database'); + } + + /** + * Bootstrap any application services. + * + * @return void + */ + public function boot() + { + $this->commands([ + Console\Commands\BootCommand::class, + Console\Commands\ConsoleMakeCommand::class, + Console\Commands\MakeCommand::class, + ]); + } +} diff --git a/src/Logging/Logger.php b/src/Logging/Logger.php new file mode 100644 index 0000000..345a1a1 --- /dev/null +++ b/src/Logging/Logger.php @@ -0,0 +1,142 @@ +console = $console; + } + + /** + * Make a new logger instance. + */ + public static function make(Command $console): Logger + { + return new static($console); + } + + /** + * System is unusable. + * + * @param mixed[] $context + */ + public function emergency(string|\Stringable $message, array $context = []): void + { + $this->console->outputComponents()->error($message); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param mixed[] $context + */ + public function alert(string|\Stringable $message, array $context = []): void + { + $this->console->outputComponents()->error($message); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param mixed[] $context + */ + public function critical(string|\Stringable $message, array $context = []): void + { + $this->error($message); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param mixed[] $context + */ + public function error(string|\Stringable $message, array $context = []): void + { + $this->console->outputComponents()->error($message); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param mixed[] $context + */ + public function warning(string|\Stringable $message, array $context = []): void + { + $this->console->outputComponents()->warn($message); + } + + /** + * Normal but significant events. + * + * @param mixed[] $context + */ + public function notice(string|\Stringable $message, array $context = []): void + { + $this->info($message); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param mixed[] $context + */ + public function info(string|\Stringable $message, array $context = []): void + { + $this->console->outputComponents()->info($message); + } + + /** + * Detailed debug information. + * + * @param mixed[] $context + */ + public function debug(string|\Stringable $message, array $context = []): void + { + if (config('app.env') === 'production') { + return; + } + + $this->info($message); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param mixed[] $context + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, string|\Stringable $message, array $context = []): void + { + $this->info($message); + } +}