-
-
Notifications
You must be signed in to change notification settings - Fork 249
Add a command example #1418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add a command example #1418
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| <?php | ||
| /** | ||
| * An example that registers a slash command with an autocomplete callback. | ||
| * | ||
| * Type "/roll" in chat, the option "sides" will trigger the autocomplete callback, listing available options. | ||
| * | ||
| * Run this example bot from main directory using command: | ||
| * php examples/command.php | ||
| */ | ||
| declare(strict_types = 1); | ||
|
|
||
| use Discord\Builders\CommandBuilder; | ||
| use Discord\Builders\MessageBuilder; | ||
| use Discord\Discord; | ||
| use Discord\Helpers\Collection; | ||
| use Discord\Parts\Interactions\ApplicationCommand; | ||
| use Discord\Parts\Interactions\ApplicationCommandAutocomplete; | ||
| use Discord\Parts\Interactions\Command\Choice; | ||
| use Discord\Parts\Interactions\Command\Command; | ||
| use Discord\Parts\Interactions\Command\Option; | ||
| use Discord\WebSockets\Intents; | ||
|
|
||
| require_once __DIR__.'/../vendor/autoload.php'; | ||
|
|
||
| ini_set('memory_limit', -1); | ||
|
|
||
| // a class to handle the command callbacks | ||
| // we're going to roll dice | ||
| class DiceRollHandler | ||
| { | ||
| public const NAME = 'roll'; | ||
|
|
||
| public function __construct( | ||
| protected Discord $discord, | ||
| ) { | ||
| // noop | ||
| } | ||
|
|
||
| public function buildCommand():CommandBuilder | ||
| { | ||
| // an option "sides" | ||
| $sides = (new Option($this->discord)) | ||
| ->setType(Option::INTEGER) | ||
| ->setName('sides') | ||
| ->setDescription('sides on the die') | ||
| ->setAutoComplete(true); | ||
|
|
||
| // the command "roll" | ||
| return (new CommandBuilder) | ||
| ->setType(Command::CHAT_INPUT) | ||
| ->setName(static::NAME) | ||
| ->setDescription('rolls an n-sided die') | ||
| ->addOption($sides); | ||
| } | ||
|
|
||
| // attempt to register a global slash command | ||
| public function register():static | ||
| { | ||
| // after the command was created successfully, you should disable this code | ||
| $this->discord->application->commands->save(new Command($this->discord, $this->buildCommand()->toArray())); | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| // add listener(s) for the command and possible subcommands | ||
| public function listen():static | ||
| { | ||
| $registeredCommand = $this->discord->listenCommand(DiceRollHandler::NAME, $this->execute(...), $this->autocomplete(...)); | ||
|
|
||
| // you may register different handlers for each subcommand here | ||
| # foreach(['subcommand1', 'subcommand2', /*...*/] as $subcommand){ | ||
| # $registeredCommand->addSubCommand($subcommand, $this->execute(...), $this->autocomplete(...)); | ||
| # } | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| // the command callback | ||
| public function execute(ApplicationCommand $interaction, Collection $params):void | ||
| { | ||
| $sides = ($interaction->data->options->offsetGet('sides')?->value ?? 20); | ||
|
|
||
| // sanity check | ||
| if (! in_array($sides, [4, 6, 8, 10, 12, 20], true)) { | ||
| $sides = 20; | ||
| } | ||
|
|
||
| $message = sprintf('%s rolled %s with a %s-sided die', $interaction->user, random_int(1, $sides), $sides); | ||
|
|
||
| // respond to the command with an interaction message | ||
| $interaction->respondWithMessage((new MessageBuilder)->setContent($message)); | ||
| } | ||
|
|
||
| // the autocomplete callback (must return array to trigger a response) | ||
| public function autocomplete(ApplicationCommandAutocomplete $interaction):array|null | ||
| { | ||
| // respond if the desired option is focused | ||
| /** @see \Discord\Parts\Interactions\Request\Option */ | ||
| if ($interaction->data->options->offsetGet('sides')->focused) { | ||
| // the dataset, e.g. fetched from a database (25 results max) | ||
| $dataset = [4, 6, 8, 10, 12, 20]; | ||
| $choices = []; | ||
|
|
||
| foreach ($dataset as $sides) { | ||
| $choices[] = new Choice($this->discord, ['name' => sprintf('%s-sided', $sides), 'value' => $sides]); | ||
| } | ||
|
|
||
| return $choices; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
| } | ||
|
|
||
| // invoke the discord client | ||
| $dc = new Discord([ | ||
| // https://discord.com/developers/applications/<APP_ID>>/bot | ||
| 'token' => 'YOUR_DISCORD_BOT_TOKEN', | ||
| // Note: MESSAGE_CONTENT, GUILD_MEMBERS and GUILD_PRESENCES are privileged, see https://dis.gd/mcfaq | ||
| 'intents' => (Intents::getDefaultIntents() | Intents::MESSAGE_CONTENT), | ||
| ]); | ||
|
|
||
| $dc->on('init', function (Discord $discord):void { | ||
| echo "Bot is ready!\n"; | ||
|
|
||
| // invoke the command handler | ||
| $commandHandler = new DiceRollHandler($discord); | ||
|
|
||
| // this method shouldn't be run on each bot start | ||
| # if($options->registerCommands){ | ||
| $commandHandler->register(); | ||
| # } | ||
|
|
||
| // add a listener for the command | ||
| $commandHandler->listen(); | ||
| }); | ||
|
|
||
| $dc->run(); | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed that the
$paramsinstance is empty here - shouldn't it contain the options that I'm fishing from$interaction->data?This is what I'm getting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It definitely should. I'll look into this more later tonight. I'm curious as to why the attributes array is empty because that is all of the data that should be sent from Discord, so the fact that it's empty means that there are no options being sent.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I just had a glance over the unit tests, and tbh I'd start there before doing anything else: replace every instance of
assertEqualswithassertSameand you'll get fun things like thisThe option "token" with value false is expected to be of type "string", but is of type "bool". I'm not sure if this is exactly related to the issue right here, but you've been testing against broken unit tests all the time. Aside of that, I've seen way too many loose comparisons throughout the codebase, which are typically sources of error. I'd suggest updating the unit tests and add proper static analysis (phan, phpstan, php-codesniffer). You will cry blood and tears, but I promise you it's worth it, because hunting for bugs will be a lot easier.Edit: Granted, I don't think the test error I cited above is caused by a loose comparison, but whatever caused that output could be replaced by a
TestCase::assertSame()too.Edit2: The mentioned error comes out of the Symfony options resolver (shudders).
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have unit tests that work, but I believe they were made to only function in the context of GitHub actions. I added dotenv to my local environment and updated DiscordTestCase as such and everything works as expected with the exception of testCanReplyToMessage:
Tests: 91, Assertions: 184, Errors: 1, Risky: 2.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By broken I mean primarily all the loose comparisons that will never reveal the real cause of an issue. PhpUnits's
assertEquals(loose comparison) should never be used unless it's absolutely necessary.This test for example does not what you think it does:
DiscordPHP/tests/CollectionsTest.php
Lines 164 to 167 in 01aae1f
Aside, there's also
assertTrueandassertFalse.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You know you can just check
$array === []right? Then there's other cases whereif(!empty()){...}is checkd and the result is handed over into a foreach without further check - in which case you should check foris_iterable()instead - in general, check for the expected types because in most casesempty()will just lead to something exploding.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe empty is also used to check for empty strings, and is meant to also be used where isset could also be checked beforehand. (e.g.
if(! isset($var) || ! $var)could be shortened to just be if (! empty($var))`. Regardless, I would like to update these to just be basic if() statements and let the truth table logic attempt to see if it's equivalent to true or false. I need to look over each case to make sure there isn't any breaking change with doing so in some places, and figure out what is appropriate where. A lot of this is legacy code and will need to be refactored as part of library maintenance.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it's painstaking - I did the same in my most popular libraries too when there were a bunch of
empty()related issues before.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wanted to quickly follow up on the topic of unit tests as this was on my mind somewhat today. We do have unit tests configured within our github workflow, but I do not believe they're currently being utilized (or contains a syntax error). The configuration is in .github/workflows/unit.yml
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is better suited for another issue or even discussion rather than a comment on an already closed issue :)