├── .github
├── coverage-badge.svg
└── workflows
│ ├── coverage.yml
│ ├── develop-branch.yml
│ ├── main-branch.yml
│ ├── pull-request.yml
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── composer.lock
├── config
└── discord.php
├── phpunit.xml
├── src
├── Channels
│ └── DiscordNotificationChannel.php
├── Contracts
│ ├── Channels
│ │ └── DiscordNotificationChannelContract.php
│ ├── Listeners
│ │ ├── ApplicationCommandInteractionEventListenerContract.php
│ │ └── MessageComponentInteractionEventListenerContract.php
│ ├── Notifications
│ │ └── DiscordNotificationContract.php
│ ├── Services
│ │ ├── DiscordApiServiceContract.php
│ │ ├── DiscordApplicationCommandServiceContract.php
│ │ └── DiscordInteractionServiceContract.php
│ └── Support
│ │ └── Builder
│ │ └── EmbedBuilderContract.php
├── Events
│ ├── AbstractInteractionEvent.php
│ ├── ApplicationCommandInteractionEvent.php
│ └── MessageComponentInteractionEvent.php
├── Providers
│ └── DiscordBotServiceProvider.php
├── Services
│ ├── DiscordApiService.php
│ ├── DiscordApplicationCommandService.php
│ └── DiscordInteractionService.php
└── Support
│ ├── Builder
│ ├── ComponentBuilder.php
│ └── EmbedBuilder.php
│ ├── Command.php
│ ├── Commands
│ ├── CommandOption.php
│ ├── MessageCommand.php
│ ├── Options
│ │ ├── AttachmentOption.php
│ │ ├── BooleanOption.php
│ │ ├── ChannelOption.php
│ │ ├── IntegerOption.php
│ │ ├── MentionableOption.php
│ │ ├── NumberOption.php
│ │ ├── OptionChoice.php
│ │ ├── RoleOption.php
│ │ ├── StringOption.php
│ │ ├── SubCommandGroupOption.php
│ │ ├── SubCommandOption.php
│ │ └── UserOption.php
│ ├── SlashCommand.php
│ └── UserCommand.php
│ ├── Component.php
│ ├── Components
│ ├── ActionRow.php
│ ├── ButtonComponent.php
│ ├── GenericButtonComponent.php
│ ├── GenericTextInputComponent.php
│ ├── LinkButtonComponent.php
│ ├── ParagraphTextInputComponent.php
│ ├── SelectMenuComponent.php
│ └── ShortTextInputComponent.php
│ ├── Embed.php
│ ├── Embeds
│ ├── AuthorEmbed.php
│ ├── FieldEmbed.php
│ ├── FooterEmbed.php
│ ├── GenericEmbed.php
│ ├── ImageEmbed.php
│ ├── ProviderEmbed.php
│ ├── ThumbnailEmbed.php
│ └── VideoEmbed.php
│ ├── Interactions
│ ├── DiscordInteractionResponse.php
│ ├── Handlers
│ │ ├── ApplicationCommandHandler.php
│ │ ├── MessageComponentInteractionHandler.php
│ │ └── PingHandler.php
│ └── InteractionHandler.php
│ ├── Objects
│ ├── AllowedMentionObject.php
│ ├── EmojiObject.php
│ └── SelectOptionObject.php
│ ├── SupportObject.php
│ └── Traits
│ ├── ApplicationCommand
│ ├── HasAutoComplete.php
│ ├── HasChoices.php
│ ├── HasOptions.php
│ └── NoChoiceTransformer.php
│ ├── DiscordApiService.php
│ ├── FiltersRecursive.php
│ ├── HasEmojiObject.php
│ ├── HasInteractionListeners.php
│ └── MergesArrays.php
└── tests
├── TestCase.php
├── Traits
├── BasicCommandOptionTests.php
└── BasicCommandTests.php
└── Unit
├── Channels
└── DiscordNotificationChannelTest.php
├── Events
├── ApplicationCommandInteractionEventTest.php
└── MessageComponentInteractionEventTest.php
├── Services
├── DiscordApiServiceTest.php
├── DiscordApplicationCommandServiceTest.php
└── DiscordInteractionServiceTest.php
└── Support
├── Builder
├── ComponentBuilderTest.php
└── EmbedBuilderTest.php
├── Commands
├── MessageCommandTest.php
├── Options
│ ├── AttachmentOptionTest.php
│ ├── BooleanOptionTest.php
│ ├── ChannelOptionTest.php
│ ├── IntegerOptionTest.php
│ ├── MentionableOptionTest.php
│ ├── NumberOptionTest.php
│ ├── OptionChoiceTest.php
│ ├── RoleOptionTest.php
│ ├── StringOptionTest.php
│ ├── SubCommandGroupOptionTest.php
│ ├── SubCommandOptionTest.php
│ └── UserOptionTest.php
├── SlashCommandTest.php
└── UserCommandTest.php
├── Components
├── ActionRowTest.php
├── ButtonComponentTest.php
├── LinkButtonComponentTest.php
├── ParagraphTextInputComponentTest.php
├── SelectMenuComponentTest.php
└── ShortTextInputComponentTest.php
├── Embeds
├── AuthorEmbedTest.php
├── FieldEmbedTest.php
├── FooterEmbedTest.php
├── GenericEmbedTest.php
├── ImageEmbedTest.php
├── ProviderEmbedTest.php
├── ThumbnailEmbedTest.php
└── VideoEmbedTest.php
├── Interactions
├── DiscordInteractionResponseTest.php
└── Handlers
│ ├── ApplicationCommandHandlerTest.php
│ ├── MessageComponentInteractionHandlerTest.php
│ └── PingHandlerTest.php
└── Objects
├── AllowedMentionObjectTest.php
├── EmojiObjectTest.php
└── SelectOptionObjectTest.php
/.github/coverage-badge.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/workflows/coverage.yml:
--------------------------------------------------------------------------------
1 | name: Coverage
2 | on:
3 | workflow_call:
4 |
5 | jobs:
6 | run-coverage:
7 | name: Run Tests with Coverage
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout code
11 | uses: actions/checkout@v2
12 | with:
13 | fetch-depth: 0
14 | - name: Composer
15 | uses: php-actions/composer@v5
16 | with:
17 | php_extensions: xdebug
18 | - name: Run Tests
19 | env:
20 | XDEBUG_MODE: coverage
21 | run: vendor/bin/phpunit
22 | - name: Commit Coverage
23 | uses: timkrase/phpunit-coverage-badge@v1.2.0
24 | with:
25 | report: clover.xml
26 | report_type: clover
27 | coverage_badge_path: ./.github/coverage-badge.svg
28 | repo_token: ${{ secrets.GH_ACCESS_TOKEN }}
29 | push_badge: true
30 |
--------------------------------------------------------------------------------
/.github/workflows/develop-branch.yml:
--------------------------------------------------------------------------------
1 | name: Develop Branch
2 | on:
3 | push:
4 | branches:
5 | - develop
6 |
7 | jobs:
8 | run-tests:
9 | name: Tests
10 | uses: ./.github/workflows/test.yml
11 | run-coverage:
12 | name: Coverage
13 | uses: ./.github/workflows/coverage.yml
14 |
--------------------------------------------------------------------------------
/.github/workflows/main-branch.yml:
--------------------------------------------------------------------------------
1 | name: Main Branch
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | run-tests:
9 | name: Tests
10 | uses: ./.github/workflows/test.yml
11 | run-coverage:
12 | name: Coverage
13 | uses: ./.github/workflows/coverage.yml
14 |
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yml:
--------------------------------------------------------------------------------
1 | name: Pull Request
2 | on:
3 | pull_request:
4 |
5 | jobs:
6 | test:
7 | name: Run Tests
8 | uses: ./.github/workflows/test.yml
9 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 | on:
3 | workflow_call:
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | php: ["7.4", "8.0", "8.1"]
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v2
14 | with:
15 | fetch-depth: 0
16 | - name: Composer
17 | uses: php-actions/composer@v5
18 | with:
19 | php_version: ${{ matrix.php }}
20 | - name: Run Tests
21 | run: vendor/bin/phpunit --no-coverage
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | .env
3 | .phpunit.result.cache
4 | tests/html-coverage
5 | clover.xml
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 nwilging
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nwilging/laravel-discord-bot",
3 | "description": "A robust Discord messaging integration for Laravel",
4 | "type": "library",
5 | "license": "MIT",
6 | "autoload": {
7 | "psr-4": {
8 | "Nwilging\\LaravelDiscordBot\\": "src/",
9 | "Nwilging\\LaravelDiscordBotTests\\": "tests/"
10 | }
11 | },
12 | "authors": [
13 | {
14 | "name": "Nicole Wilging",
15 | "email": "nicole@wilging.com"
16 | }
17 | ],
18 | "require": {
19 | "ext-sodium": "*",
20 | "php": ">=7.4",
21 | "laravel/framework": ">=8",
22 | "guzzlehttp/guzzle": "^7.4"
23 | },
24 | "extra": {
25 | "laravel": {
26 | "providers": [
27 | "Nwilging\\LaravelDiscordBot\\Providers\\DiscordBotServiceProvider"
28 | ]
29 | }
30 | },
31 | "require-dev": {
32 | "phpunit/phpunit": "^9.5",
33 | "mockery/mockery": "^1.5"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/config/discord.php:
--------------------------------------------------------------------------------
1 | env('DISCORD_API_BOT_TOKEN'),
11 | 'api_url' => env('DISCORD_API_URL', 'https://discord.com/api'),
12 | 'application_id' => env('DISCORD_APPLICATION_ID'),
13 | 'public_key' => env('DISCORD_PUBLIC_KEY'),
14 | 'interactions' => [
15 | 'component_interaction_default_behavior' => (in_array(
16 | env('DISCORD_COMPONENT_INTERACTION_DEFAULT_BEHAVIOR'),
17 | $allowedDefaultBehaviorTypes
18 | )) ? env('DISCORD_COMPONENT_INTERACTION_DEFAULT_BEHAVIOR') : 'defer',
19 | ],
20 | ];
21 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | ./tests/Unit
10 |
11 |
12 |
13 |
14 | ./src
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/Channels/DiscordNotificationChannel.php:
--------------------------------------------------------------------------------
1 | discordApiService = $discordApiService;
17 | }
18 |
19 | public function send($notifiable, DiscordNotificationContract $notification): array
20 | {
21 | $notificationArray = $notification->toDiscord($notifiable);
22 | switch ($notificationArray['contentType']) {
23 | case 'plain':
24 | return $this->handleTextMessage($notificationArray);
25 | case 'rich':
26 | return $this->handleRichTextMessage($notificationArray);
27 | default:
28 | throw new \InvalidArgumentException(sprintf('%s is not a valid contentType', $notificationArray['contentType']));
29 | }
30 | }
31 |
32 | protected function handleTextMessage(array $notificationArray): array
33 | {
34 | $channelId = $notificationArray['channelId'];
35 | $message = $notificationArray['message'];
36 | $options = $notificationArray['options'] ?? [];
37 |
38 | return $this->discordApiService->sendTextMessage($channelId, $message, $options);
39 | }
40 |
41 | protected function handleRichTextMessage(array $notificationArray): array
42 | {
43 | $channelId = $notificationArray['channelId'];
44 | $embeds = $notificationArray['embeds'] ?? [];
45 | $components = $notificationArray['components'] ?? [];
46 | $options = $notificationArray['options'] ?? [];
47 |
48 | return $this->discordApiService->sendRichTextMessage($channelId, $embeds, $components, $options);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Contracts/Channels/DiscordNotificationChannelContract.php:
--------------------------------------------------------------------------------
1 | interactionRequest = $interactionRequest;
18 | }
19 |
20 | public function getInteractionRequest(): ParameterBag
21 | {
22 | return $this->interactionRequest;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Events/ApplicationCommandInteractionEvent.php:
--------------------------------------------------------------------------------
1 | interactionRequest->get('data', []);
11 | }
12 |
13 | public function getCommandName(): string
14 | {
15 | return $this->getData()['name'];
16 | }
17 |
18 | public function getCommandId(): string
19 | {
20 | return $this->getData()['id'];
21 | }
22 |
23 | public function getCommandType(): int
24 | {
25 | return (int) $this->getData()['type'];
26 | }
27 |
28 | public function getChannelId(): string
29 | {
30 | return $this->interactionRequest->get('channel_id');
31 | }
32 |
33 | public function getApplicationId(): string
34 | {
35 | return $this->interactionRequest->get('application_id');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Events/MessageComponentInteractionEvent.php:
--------------------------------------------------------------------------------
1 | mergeConfigFrom(__DIR__ . '/../../config/discord.php', 'discord');
28 | }
29 |
30 | public function register()
31 | {
32 | Notification::resolved(function (ChannelManager $channelManager): void {
33 | $channelManager->extend('discord', function (): DiscordNotificationChannelContract {
34 | return $this->app->make(DiscordNotificationChannelContract::class);
35 | });
36 | });
37 |
38 | $this->app->bind(ClientInterface::class, Client::class);
39 |
40 | $this->app->bind(DiscordApiServiceContract::class, DiscordApiService::class);
41 | $this->app->when(DiscordApiService::class)->needs('$token')->give(function (): string {
42 | return $this->app->make(Config::class)->get('discord.token');
43 | });
44 |
45 | $this->app->when(DiscordApiService::class)->needs('$apiUrl')->give(function (): string {
46 | return $this->app->make(Config::class)->get('discord.api_url');
47 | });
48 |
49 | $this->app->bind(DiscordInteractionServiceContract::class, DiscordInteractionService::class);
50 | $this->app->when(DiscordInteractionService::class)->needs('$applicationId')->give(function (): string {
51 | return $this->app->make(Config::class)->get('discord.application_id');
52 | });
53 |
54 | $this->app->when(DiscordInteractionService::class)->needs('$publicKey')->give(function (): string {
55 | return $this->app->make(Config::class)->get('discord.public_key');
56 | });
57 |
58 | $this->app->when(MessageComponentInteractionHandler::class)->needs('$defaultBehavior')->give(function (): string {
59 | return $this->app->make(Config::class)->get('discord.interactions.component_interaction_default_behavior');
60 | });
61 |
62 | $this->app->when(ApplicationCommandHandler::class)->needs('$defaultBehavior')->give(function (): string {
63 | return $this->app->make(Config::class)->get('discord.interactions.component_interaction_default_behavior');
64 | });
65 |
66 | $this->app->bind(DiscordNotificationChannelContract::class, DiscordNotificationChannel::class);
67 |
68 | $this->app->bind(DiscordApplicationCommandServiceContract::class, DiscordApplicationCommandService::class);
69 |
70 | $this->app->when(DiscordApplicationCommandService::class)->needs('$token')->give(function (): string {
71 | return $this->app->make(Config::class)->get('discord.token');
72 | });
73 |
74 | $this->app->when(DiscordApplicationCommandService::class)->needs('$apiUrl')->give(function (): string {
75 | return $this->app->make(Config::class)->get('discord.api_url');
76 | });
77 |
78 | $this->app->when(DiscordApplicationCommandService::class)->needs('$applicationId')->give(function (): string {
79 | return $this->app->make(Config::class)->get('discord.application_id');
80 | });
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Services/DiscordApiService.php:
--------------------------------------------------------------------------------
1 | token = $token;
19 | $this->apiUrl = $apiUrl;
20 | $this->httpClient = $httpClient;
21 | }
22 |
23 | public function sendTextMessage(string $channelId, string $message, array $options = []): array
24 | {
25 | $response = $this->makeRequest(
26 | 'POST',
27 | sprintf('channels/%s/messages', $channelId),
28 | array_merge($this->buildMessageOptions($options), [
29 | 'content' => $message,
30 | ]),
31 | );
32 |
33 | return json_decode($response->getBody()->getContents(), true);
34 | }
35 |
36 | public function sendRichTextMessage(string $channelId, array $embeds, array $components = [], array $options = []): array
37 | {
38 | $embedArrays = array_map(function (Embed $embed): array {
39 | return $embed->toArray();
40 | }, $embeds);
41 |
42 | $componentArrays = array_map(function (Component $component): array {
43 | return $component->toArray();
44 | }, $components);
45 |
46 | $response = $this->makeRequest(
47 | 'POST',
48 | sprintf('channels/%s/messages', $channelId),
49 | array_merge($this->buildMessageOptions($options), [
50 | 'embeds' => $embedArrays,
51 | 'components' => $componentArrays,
52 | ]),
53 | );
54 |
55 | return json_decode($response->getBody()->getContents(), true);
56 | }
57 |
58 | protected function buildMessageOptions(array $options): array
59 | {
60 | return [];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Services/DiscordApplicationCommandService.php:
--------------------------------------------------------------------------------
1 | applicationId = $applicationId;
21 | $this->token = $token;
22 | $this->apiUrl = $apiUrl;
23 | $this->httpClient = $httpClient;
24 | }
25 |
26 | public function createGlobalCommand(Command $command): array
27 | {
28 | $response = $this->makeRequest(
29 | Request::METHOD_POST,
30 | sprintf('applications/%s/commands', $this->applicationId),
31 | $command->toArray(),
32 | );
33 |
34 | return json_decode($response->getBody()->getContents(), true);
35 | }
36 |
37 | public function createGuildCommand(string $guildId, Command $command): array
38 | {
39 | $response = $this->makeRequest(
40 | Request::METHOD_POST,
41 | sprintf('applications/%s/guilds/%s/commands', $this->applicationId, $guildId),
42 | $command->toArray(),
43 | );
44 |
45 | return json_decode($response->getBody()->getContents(), true);
46 | }
47 |
48 | public function deleteGlobalCommand(string $commandId): void
49 | {
50 | $this->makeRequest(
51 | Request::METHOD_DELETE,
52 | sprintf('applications/%s/commands/%s', $this->applicationId, $commandId)
53 | );
54 | }
55 |
56 | public function deleteGuildCommand(string $guildId, string $commandId): void
57 | {
58 | $this->makeRequest(
59 | Request::METHOD_DELETE,
60 | sprintf('applications/%s/guilds/%s/commands/%s', $this->applicationId, $guildId, $commandId)
61 | );
62 | }
63 |
64 | public function updateGlobalCommand(string $commandId, Command $command): array
65 | {
66 | $response = $this->makeRequest(
67 | Request::METHOD_PATCH,
68 | sprintf('applications/%s/commands/%s', $this->applicationId, $commandId),
69 | $command->toArray()
70 | );
71 |
72 | return json_decode($response->getBody()->getContents(), true);
73 | }
74 |
75 | public function updateGuildCommand(string $guildId, string $commandId, Command $command): array
76 | {
77 | $response = $this->makeRequest(
78 | Request::METHOD_PATCH,
79 | sprintf('applications/%s/guilds/%s/commands/%s', $this->applicationId, $guildId, $commandId),
80 | $command->toArray()
81 | );
82 |
83 | return json_decode($response->getBody()->getContents(), true);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Services/DiscordInteractionService.php:
--------------------------------------------------------------------------------
1 | PingHandler::class,
28 | InteractionHandler::REQUEST_TYPE_APPLICATION_COMMAND => ApplicationCommandHandler::class,
29 | InteractionHandler::REQUEST_TYPE_MESSAGE_COMPONENT => MessageComponentInteractionHandler::class,
30 | ];
31 |
32 | public function __construct(string $applicationId, string $publicKey, Application $laravel)
33 | {
34 | $this->applicationId = $applicationId;
35 | $this->publicKey = $publicKey;
36 | $this->laravel = $laravel;
37 | }
38 |
39 | public function handleInteractionRequest(Request $request): DiscordInteractionResponse
40 | {
41 | $this->validate($request);
42 | $json = $request->json()->all();
43 |
44 | $handlerClass = $this->interactionHandlers[$json['type']] ?? null;
45 | if (!$handlerClass) {
46 | throw new NotFoundHttpException();
47 | }
48 |
49 | $handler = $this->laravel->make($handlerClass);
50 | return $handler->handle($request);
51 | }
52 |
53 | protected function validate(Request $request): void
54 | {
55 | $signature = $request->header('X-Signature-Ed25519');
56 | $timestamp = $request->header('X-Signature-Timestamp');
57 | $body = $request->getContent();
58 |
59 | if (!$signature || !$timestamp || !$body) {
60 | throw new UnauthorizedHttpException('invalid request signature');
61 | }
62 |
63 | $data = sprintf('%s%s', $timestamp, $body);
64 | try {
65 | $verified = sodium_crypto_sign_verify_detached(hex2bin($signature), $data, hex2bin($this->publicKey));
66 | } catch (\SodiumException $exception) {
67 | throw new UnauthorizedHttpException('invalid request signature');
68 | }
69 |
70 | if (!$verified) {
71 | throw new UnauthorizedHttpException('invalid request signature');
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Support/Builder/ComponentBuilder.php:
--------------------------------------------------------------------------------
1 | components[] = $component;
22 | return $this;
23 | }
24 |
25 | public function addActionButton(string $label, string $customId): self
26 | {
27 | $this->components[] = new ButtonComponent($label, $customId);
28 | return $this;
29 | }
30 |
31 | public function addLinkButton(string $label, string $url): self
32 | {
33 | $this->components[] = new LinkButtonComponent($label, $url);
34 | return $this;
35 | }
36 |
37 | /**
38 | * @param SelectOptionObject[] $options
39 | * @param string $customId
40 | * @return SelectMenuComponent
41 | */
42 | public function addSelectMenuComponent(array $options, string $customId): self
43 | {
44 | $this->components[] = new SelectMenuComponent($customId, $options);
45 | return $this;
46 | }
47 |
48 | public function withSelectOptionObject(string $label, string $value): SelectOptionObject
49 | {
50 | return new SelectOptionObject($label, $value);
51 | }
52 |
53 | public function addShortTextInput(string $label, string $customId): self
54 | {
55 | $this->components[] = new ShortTextInputComponent($label, $customId);
56 | return $this;
57 | }
58 |
59 | public function addParagraphTextInput(string $label, string $customId): self
60 | {
61 | $this->components[] = new ParagraphTextInputComponent($label, $customId);
62 | return $this;
63 | }
64 |
65 | public function getActionRow(): ActionRow
66 | {
67 | return new ActionRow($this->components);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Support/Builder/EmbedBuilder.php:
--------------------------------------------------------------------------------
1 | embeds[] = $embed;
25 | return $this;
26 | }
27 |
28 | public function addFooter(string $text): self
29 | {
30 | $this->addEmbed(new FooterEmbed($text));
31 | return $this;
32 | }
33 |
34 | public function addImage(string $url): self
35 | {
36 | $this->addEmbed(new ImageEmbed($url));
37 | return $this;
38 | }
39 |
40 | public function addThumbnail(string $url): self
41 | {
42 | $this->addEmbed(new ThumbnailEmbed($url));
43 | return $this;
44 | }
45 |
46 | public function addVideo(string $url): self
47 | {
48 | $this->addEmbed(new VideoEmbed($url));
49 | return $this;
50 | }
51 |
52 | public function addProvider(string $name, string $url): self
53 | {
54 | $embed = new ProviderEmbed();
55 | $embed->withName($name)->withUrl($url);
56 |
57 | $this->addEmbed($embed);
58 | return $this;
59 | }
60 |
61 | public function addAuthor(string $name): self
62 | {
63 | $this->addEmbed(new AuthorEmbed($name));
64 | return $this;
65 | }
66 |
67 | public function getEmbeds(): array
68 | {
69 | return $this->embeds;
70 | }
71 |
72 | public function toArray(): array
73 | {
74 | return array_map(function (Embed $embed): array {
75 | return $embed->toArray();
76 | }, $this->embeds);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Support/Command.php:
--------------------------------------------------------------------------------
1 | name = $name;
46 | }
47 |
48 | public function parentApplication(string $applicationId): self
49 | {
50 | $this->parentApplicationId = $applicationId;
51 | return $this;
52 | }
53 |
54 | public function nameLocalizations(array $localizations): self
55 | {
56 | $this->nameLocalizations = $localizations;
57 | return $this;
58 | }
59 |
60 | public function descriptionLocalizations(array $localizations): self
61 | {
62 | $this->descriptionLocalizations = $localizations;
63 | return $this;
64 | }
65 |
66 | /**
67 | * Set of permissions represented as a bit set
68 | *
69 | * @see https://discord.com/developers/docs/topics/permissions
70 | *
71 | * @param string $permission
72 | * @return $this
73 | */
74 | public function defaultMemberPermissions(string $permission): self
75 | {
76 | $this->defaultMemberPermissions = $permission;
77 | return $this;
78 | }
79 |
80 | public function dmPermission(bool $enable = true): self
81 | {
82 | $this->dmPermission = $enable;
83 | return $this;
84 | }
85 |
86 | public function defaultPermission(bool $enable = true): self
87 | {
88 | $this->defaultPermission = $enable;
89 | return $this;
90 | }
91 |
92 | public function nsfw(bool $enable = true): self
93 | {
94 | $this->nsfw = $enable;
95 | return $this;
96 | }
97 |
98 | public function version(string $version): self
99 | {
100 | $this->version = $version;
101 | return $this;
102 | }
103 |
104 | public abstract function getType(): int;
105 |
106 | public function toArray(): array
107 | {
108 | return $this->arrayFilterRecursive([
109 | 'type' => $this->getType(),
110 | 'name' => $this->name,
111 | 'application_id' => $this->parentApplicationId,
112 | 'name_localizations' => $this->nameLocalizations,
113 | 'description_localizations' => $this->descriptionLocalizations,
114 | 'default_member_permissions' => $this->defaultMemberPermissions,
115 | 'dm_permission' => $this->dmPermission,
116 | 'default_permission' => $this->defaultPermission,
117 | 'nsfw' => $this->nsfw,
118 | 'version' => $this->version,
119 | ]);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/Support/Commands/CommandOption.php:
--------------------------------------------------------------------------------
1 | name = $name;
52 | $this->description = $description;
53 | }
54 |
55 | public abstract function getType(): int;
56 |
57 | protected abstract function choiceTransformer(OptionChoice $choice): array;
58 |
59 | /**
60 | * If the parameter is required or optional
61 | *
62 | * @see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure
63 | *
64 | * @param bool $required
65 | * @return $this
66 | */
67 | public function required(bool $required = true): self
68 | {
69 | $this->required = $required;
70 | return $this;
71 | }
72 |
73 | /**
74 | * Localization dictionary for the `name` field. Values follow the same restrictions as `name`
75 | *
76 | * @see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure
77 | *
78 | * @param array $localizations
79 | * @return $this
80 | */
81 | public function nameLocalizations(array $localizations): self
82 | {
83 | $this->nameLocalizations = $localizations;
84 | return $this;
85 | }
86 |
87 | /**
88 | * Localization dictionary for the `description` field. Values follow the same restrictions as `description`
89 | *
90 | * @see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure
91 | *
92 | * @param array $localizations
93 | * @return $this
94 | */
95 | public function descriptionLocalizations(array $localizations): self
96 | {
97 | $this->descriptionLocalizations = $localizations;
98 | return $this;
99 | }
100 |
101 | public function toArray(): array
102 | {
103 | return $this->arrayFilterRecursive([
104 | 'type' => $this->getType(),
105 | 'name' => $this->name,
106 | 'description' => $this->description,
107 | 'required' => $this->required,
108 | 'name_localizations' => $this->nameLocalizations,
109 | 'description_localizations' => $this->descriptionLocalizations,
110 | ]);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Support/Commands/MessageCommand.php:
--------------------------------------------------------------------------------
1 | channelTypes = $types;
30 | return $this;
31 | }
32 |
33 | public function getType(): int
34 | {
35 | return static::TYPE_CHANNEL;
36 | }
37 |
38 | public function toArray(): array
39 | {
40 | return $this->toMergedArray([
41 | 'channel_types' => $this->channelTypes,
42 | ]);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Support/Commands/Options/IntegerOption.php:
--------------------------------------------------------------------------------
1 | minValue = $minValue;
35 | return $this;
36 | }
37 |
38 | /**
39 | * The maximum value permitted
40 | *
41 | * @see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure
42 | *
43 | * @param int $maxValue
44 | * @return $this
45 | */
46 | public function maxValue(int $maxValue): self
47 | {
48 | $this->maxValue = $maxValue;
49 | return $this;
50 | }
51 |
52 | protected function choiceTransformer(OptionChoice $choice): array
53 | {
54 | $array = $choice->toArray();
55 | $array['value'] = intval($array['value']);
56 |
57 | return $array;
58 | }
59 |
60 | public function toArray(): array
61 | {
62 | $merge = $this->mergeChoices([]);
63 | $merge = $this->mergeAutocomplete($merge);
64 |
65 | return $this->toMergedArray(array_merge($merge, [
66 | 'min_value' => $this->minValue,
67 | 'max_value' => $this->maxValue,
68 | ]));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Support/Commands/Options/MentionableOption.php:
--------------------------------------------------------------------------------
1 | toArray();
27 | $array['value'] = (float) $array['value'];
28 |
29 | return $array;
30 | }
31 |
32 | /**
33 | * The minimum value permitted
34 | *
35 | * @see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure
36 | *
37 | * @param float $minValue
38 | * @return $this
39 | */
40 | public function minValue(float $minValue): self
41 | {
42 | $this->minValue = $minValue;
43 | return $this;
44 | }
45 |
46 | /**
47 | * The maximum value permitted
48 | *
49 | * @see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure
50 | *
51 | * @param float $maxValue
52 | * @return $this
53 | */
54 | public function maxValue(float $maxValue): self
55 | {
56 | $this->maxValue = $maxValue;
57 | return $this;
58 | }
59 |
60 | public function toArray(): array
61 | {
62 | $merge = $this->mergeChoices([]);
63 | $merge = $this->mergeAutocomplete($merge);
64 |
65 | return $this->toMergedArray(array_merge($merge, [
66 | 'min_value' => $this->minValue,
67 | 'max_value' => $this->maxValue,
68 | ]));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Support/Commands/Options/OptionChoice.php:
--------------------------------------------------------------------------------
1 | name = $name;
26 | $this->value = $value;
27 | }
28 |
29 | public function nameLocalizations(array $localizations): self
30 | {
31 | $this->nameLocalizations = $localizations;
32 | return $this;
33 | }
34 |
35 | public function toArray(): array
36 | {
37 | return $this->arrayFilterRecursive([
38 | 'name' => $this->name,
39 | 'name_localizations' => $this->nameLocalizations,
40 | 'value' => $this->value,
41 | ]);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Support/Commands/Options/RoleOption.php:
--------------------------------------------------------------------------------
1 | maxLength = $maxLength;
25 | return $this;
26 | }
27 |
28 | protected function choiceTransformer(OptionChoice $choice): array
29 | {
30 | $array = $choice->toArray();
31 | $array['value'] = (string) $array['value'];
32 |
33 | return $array;
34 | }
35 |
36 | public function toArray(): array
37 | {
38 | $merge = $this->mergeChoices([]);
39 | $merge = $this->mergeAutocomplete($merge);
40 |
41 | return $this->toMergedArray(array_merge($merge, [
42 | 'max_length' => $this->maxLength,
43 | ]));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Support/Commands/Options/SubCommandGroupOption.php:
--------------------------------------------------------------------------------
1 | description = $description;
20 | }
21 |
22 | public function getType(): int
23 | {
24 | return static::TYPE_CHAT_INPUT;
25 | }
26 |
27 | public function toArray(): array
28 | {
29 | return $this->toMergedArray($this->mergeOptions([
30 | 'description' => $this->description,
31 | ]));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Support/Commands/UserCommand.php:
--------------------------------------------------------------------------------
1 | customId = $customId;
27 | }
28 |
29 | public abstract function getType(): int;
30 |
31 | /**
32 | * Returns a Discord-API compliant component array
33 | *
34 | * @see https://discord.com/developers/docs/interactions/message-components#component-object
35 | *
36 | * @return array
37 | */
38 | public function toArray(): array
39 | {
40 | return $this->arrayFilterRecursive([
41 | 'type' => $this->getType(),
42 | 'custom_id' => $this->customId,
43 | ]);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Support/Components/ActionRow.php:
--------------------------------------------------------------------------------
1 | components = $components;
25 | }
26 |
27 | public function addComponent(Component $component): self
28 | {
29 | $this->components[] = $component;
30 | return $this;
31 | }
32 |
33 | public function getType(): int
34 | {
35 | return static::TYPE_ACTION_ROW;
36 | }
37 |
38 | public function toArray(): array
39 | {
40 | return $this->toMergedArray([
41 | 'components' => array_map(function (Component $component): array {
42 | return $component->toArray();
43 | }, $this->components),
44 | ]);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Support/Components/ButtonComponent.php:
--------------------------------------------------------------------------------
1 | style = static::STYLE_PRIMARY;
31 | return $this;
32 | }
33 |
34 | /**
35 | * Sets the button style to secondary
36 | *
37 | * @see https://discord.com/developers/docs/interactions/message-components#button-object-button-styles
38 | * @return $this
39 | */
40 | public function withSecondaryStyle(): self
41 | {
42 | $this->style = static::STYLE_SECONDARY;
43 | return $this;
44 | }
45 |
46 | /**
47 | * Sets the button style to success
48 | *
49 | * @see https://discord.com/developers/docs/interactions/message-components#button-object-button-styles
50 | * @return $this
51 | */
52 | public function withSuccessStyle(): self
53 | {
54 | $this->style = static::STYLE_SUCCESS;
55 | return $this;
56 | }
57 |
58 | /**
59 | * Sets the button style to danger
60 | *
61 | * @see https://discord.com/developers/docs/interactions/message-components#button-object-button-styles
62 | * @return $this
63 | */
64 | public function withDangerStyle(): self
65 | {
66 | $this->style = static::STYLE_DANGER;
67 | return $this;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Support/Components/GenericButtonComponent.php:
--------------------------------------------------------------------------------
1 | style = $style;
31 | $this->label = $label;
32 | }
33 |
34 | /**
35 | * Whether the button is disabled
36 | *
37 | * @see https://discord.com/developers/docs/interactions/message-components#button-object-button-structure
38 | * @param bool $disabled
39 | * @return $this
40 | */
41 | public function disabled(bool $disabled = true): self
42 | {
43 | $this->disabled = $disabled;
44 | return $this;
45 | }
46 |
47 | public function getType(): int
48 | {
49 | return static::TYPE_BUTTON;
50 | }
51 |
52 | public function toArray(): array
53 | {
54 | return $this->arrayFilterRecursive($this->mergeEmojiObject([
55 | 'type' => $this->getType(),
56 | 'custom_id' => $this->customId,
57 | 'style' => $this->style,
58 | 'label' => $this->label,
59 | 'disabled' => $this->disabled,
60 | ]));
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Support/Components/GenericTextInputComponent.php:
--------------------------------------------------------------------------------
1 | style = $style;
35 | $this->label = $label;
36 | }
37 |
38 | /**
39 | * The minimum input length for a text input, min 0, max 4000
40 | *
41 | * @see https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure
42 | * @param int $minLength
43 | * @return $this
44 | */
45 | public function withMinLength(int $minLength): self
46 | {
47 | $this->minLength = $minLength;
48 | return $this;
49 | }
50 |
51 | /**
52 | * The maximum input length for a text input, min 1, max 4000
53 | *
54 | * @see https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure
55 | * @param int $maxLength
56 | * @return $this
57 | */
58 | public function withMaxLength(int $maxLength): self
59 | {
60 | $this->maxLength = $maxLength;
61 | return $this;
62 | }
63 |
64 | /**
65 | * Custom placeholder text if the input is empty, max 100 characters
66 | *
67 | * @see https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure
68 | * @param string $placeholder
69 | * @return $this
70 | */
71 | public function withPlaceholder(string $placeholder): self
72 | {
73 | $this->placeholder = $placeholder;
74 | return $this;
75 | }
76 |
77 | /**
78 | * A pre-filled value for this component, max 4000 characters
79 | *
80 | * @see https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure
81 | * @param string $value
82 | * @return $this
83 | */
84 | public function withValue(string $value): self
85 | {
86 | $this->value = $value;
87 | return $this;
88 | }
89 |
90 | /**
91 | * Whether this component is required to be filled, default true
92 | *
93 | * @see https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure
94 | * @param bool $required
95 | * @return $this
96 | */
97 | public function required(bool $required = true): self
98 | {
99 | $this->required = $required;
100 | return $this;
101 | }
102 |
103 | public function getType(): int
104 | {
105 | return static::TYPE_TEXT_INPUT;
106 | }
107 |
108 | /**
109 | * Returns a Discord-API compatible text input array
110 | *
111 | * @see https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure
112 | * @return array
113 | */
114 | public function toArray(): array
115 | {
116 | return $this->arrayFilterRecursive([
117 | 'type' => $this->getType(),
118 | 'custom_id' => $this->customId,
119 | 'style' => $this->style,
120 | 'label' => $this->label,
121 | 'min_length' => $this->minLength,
122 | 'max_length' => $this->maxLength,
123 | 'required' => $this->required,
124 | 'value' => $this->value,
125 | 'placeholder' => $this->placeholder,
126 | ]);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Support/Components/LinkButtonComponent.php:
--------------------------------------------------------------------------------
1 | url = $url;
22 | }
23 |
24 | public function toArray(): array
25 | {
26 | return $this->toMergedArray([
27 | 'url' => $this->url,
28 | ]);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Support/Components/ParagraphTextInputComponent.php:
--------------------------------------------------------------------------------
1 | options = $options;
35 | }
36 |
37 | /**
38 | * Custom placeholder text if nothing is selected, max 150 characters
39 | *
40 | * @see https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure
41 | * @param string $placeholder
42 | * @return $this
43 | */
44 | public function withPlaceholder(string $placeholder): self
45 | {
46 | $this->placeholder = $placeholder;
47 | return $this;
48 | }
49 |
50 | /**
51 | * The minimum number of items that must be chosen; default 1, min 0, max 25
52 | *
53 | * @see https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure
54 | * @param int $minValues
55 | * @return $this
56 | */
57 | public function withMinValues(int $minValues): self
58 | {
59 | $this->minValues = $minValues;
60 | return $this;
61 | }
62 |
63 | /**
64 | * The maximum number of items that can be chosen; default 1, max 25
65 | *
66 | * @see https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure
67 | * @param int $maxValues
68 | * @return $this
69 | */
70 | public function withMaxValues(int $maxValues): self
71 | {
72 | $this->maxValues = $maxValues;
73 | return $this;
74 | }
75 |
76 | /**
77 | * Disables the select
78 | *
79 | * @see https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure
80 | * @param bool $disabled
81 | * @return $this
82 | */
83 | public function disabled(bool $disabled = true): self
84 | {
85 | $this->disabled = $disabled;
86 | return $this;
87 | }
88 |
89 | public function getType(): int
90 | {
91 | return static::TYPE_SELECT_MENU;
92 | }
93 |
94 | public function toArray(): array
95 | {
96 | return $this->toMergedArray([
97 | 'options' => array_map(function (SelectOptionObject $option): array {
98 | return $option->toArray();
99 | }, $this->options),
100 | 'placeholder' => $this->placeholder,
101 | 'min_values' => $this->minValues,
102 | 'max_values' => $this->maxValues,
103 | 'disabled' => $this->disabled,
104 | ]);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Support/Components/ShortTextInputComponent.php:
--------------------------------------------------------------------------------
1 | title = $title;
40 | $this->description = $description;
41 | $this->timestamp = $timestamp;
42 | }
43 |
44 | /**
45 | * The color code of the embed
46 | *
47 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-structure
48 | *
49 | * @param int $colorCode
50 | * @return $this
51 | */
52 | public function withColor(int $colorCode): self
53 | {
54 | $this->color = $colorCode;
55 | return $this;
56 | }
57 |
58 | public abstract function getType(): string;
59 |
60 | /**
61 | * Returns a Discord-API compliant embed array
62 | *
63 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-structure
64 | *
65 | * @return array
66 | */
67 | public function toArray(): array
68 | {
69 | return $this->arrayFilterRecursive([
70 | 'type' => $this->getType(),
71 | 'title' => $this->title,
72 | 'description' => $this->description,
73 | 'timestamp' => $this->timestamp,
74 | 'color' => $this->color,
75 | ]);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Support/Embeds/AuthorEmbed.php:
--------------------------------------------------------------------------------
1 | name = $name;
29 | }
30 |
31 | /**
32 | * URL of author
33 | *
34 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure
35 | * @param string $url
36 | * @return $this
37 | */
38 | public function withUrl(string $url): self
39 | {
40 | $this->url = $url;
41 | return $this;
42 | }
43 |
44 | /**
45 | * URL of author icon (only supports http(s) and attachments)
46 | *
47 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure
48 | * @param string $iconUrl
49 | * @return $this
50 | */
51 | public function withIconUrl(string $iconUrl): self
52 | {
53 | $this->iconUrl = $iconUrl;
54 | return $this;
55 | }
56 |
57 | /**
58 | * A proxied url of author icon
59 | *
60 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure
61 | * @param string $proxyIconUrl
62 | * @return $this
63 | */
64 | public function withProxyIconUrl(string $proxyIconUrl): self
65 | {
66 | $this->proxyIconUrl = $proxyIconUrl;
67 | return $this;
68 | }
69 |
70 | public function getType(): string
71 | {
72 | return static::TYPE_AUTHOR;
73 | }
74 |
75 | public function toArray(): array
76 | {
77 | return $this->toMergedArray([
78 | 'author' => [
79 | 'name' => $this->name,
80 | 'url' => $this->url,
81 | 'icon_url' => $this->iconUrl,
82 | 'proxy_icon_url' => $this->proxyIconUrl,
83 | ],
84 | ]);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Support/Embeds/FieldEmbed.php:
--------------------------------------------------------------------------------
1 | name = $name;
28 | $this->value = $value;
29 | }
30 |
31 | /**
32 | * Whether this field should display inline
33 | *
34 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure
35 | * @param bool $inline
36 | * @return $this
37 | */
38 | public function inline(bool $inline = true): self
39 | {
40 | $this->inline = $inline;
41 | return $this;
42 | }
43 |
44 | public function getType(): string
45 | {
46 | return static::TYPE_FIELD;
47 | }
48 |
49 | public function toArray(): array
50 | {
51 | return $this->toMergedArray([
52 | 'field' => [
53 | 'name' => $this->name,
54 | 'value' => $this->value,
55 | 'inline' => $this->inline,
56 | ],
57 | ]);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Support/Embeds/FooterEmbed.php:
--------------------------------------------------------------------------------
1 | text = $text;
27 | }
28 |
29 | /**
30 | * URL of footer icon (only supports http(s) and attachments)
31 | *
32 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
33 | * @param string $iconUrl
34 | * @return $this
35 | */
36 | public function withIconUrl(string $iconUrl): self
37 | {
38 | $this->iconUrl = $iconUrl;
39 | return $this;
40 | }
41 |
42 | /**
43 | * A proxied url of footer icon
44 | *
45 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
46 | * @param string $proxyIconUrl
47 | * @return $this
48 | */
49 | public function withProxyIconUrl(string $proxyIconUrl): self
50 | {
51 | $this->proxyIconUrl = $proxyIconUrl;
52 | return $this;
53 | }
54 |
55 | public function getType(): string
56 | {
57 | return static::TYPE_FOOTER;
58 | }
59 |
60 | public function toArray(): array
61 | {
62 | return $this->toMergedArray([
63 | 'footer' => [
64 | 'text' => $this->text,
65 | 'icon_url' => $this->iconUrl,
66 | 'proxy_icon_url' => $this->proxyIconUrl,
67 | ],
68 | ]);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Support/Embeds/GenericEmbed.php:
--------------------------------------------------------------------------------
1 | author = $author;
42 | return $this;
43 | }
44 |
45 | public function withFooter(FooterEmbed $footer): self
46 | {
47 | $this->footer = $footer;
48 | return $this;
49 | }
50 |
51 | public function withImage(ImageEmbed $image): self
52 | {
53 | $this->image = $image;
54 | return $this;
55 | }
56 |
57 | public function withThumbnail(ThumbnailEmbed $thumbnail): self
58 | {
59 | $this->thumbnail = $thumbnail;
60 | return $this;
61 | }
62 |
63 | public function withVideo(VideoEmbed $video): self
64 | {
65 | $this->video = $video;
66 | return $this;
67 | }
68 |
69 | public function withProvider(ProviderEmbed $provider): self
70 | {
71 | $this->provider = $provider;
72 | return $this;
73 | }
74 |
75 | public function addField(FieldEmbed $field): self
76 | {
77 | $this->fields[] = $field;
78 | return $this;
79 | }
80 |
81 | public function getType(): string
82 | {
83 | return static::TYPE_RICH;
84 | }
85 |
86 | public function toArray(): array
87 | {
88 | return $this->toMergedArray([
89 | 'footer' => $this->footer?->toArray(),
90 | 'image' => $this->image?->toArray(),
91 | 'thumbnail' => $this->thumbnail?->toArray(),
92 | 'video' => $this->video?->toArray(),
93 | 'provider' => $this->provider?->toArray(),
94 | 'author' => $this->author?->toArray(),
95 | 'fields' => array_map(function (FieldEmbed $field): array {
96 | return $field->toArray()['field'];
97 | }, $this->fields),
98 | ]);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/Support/Embeds/ImageEmbed.php:
--------------------------------------------------------------------------------
1 | url = $url;
29 | }
30 |
31 | /**
32 | * A proxied url of the image
33 | *
34 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure
35 | * @param string $proxyUrl
36 | * @return $this
37 | */
38 | public function withProxyUrl(string $proxyUrl): self
39 | {
40 | $this->proxyUrl = $proxyUrl;
41 | return $this;
42 | }
43 |
44 | /**
45 | * Image height
46 | *
47 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure
48 | * @param int $height
49 | * @return $this
50 | */
51 | public function withHeight(int $height): self
52 | {
53 | $this->height = $height;
54 | return $this;
55 | }
56 |
57 | /**
58 | * Image width
59 | *
60 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure
61 | * @param int $width
62 | * @return $this
63 | */
64 | public function withWidth(int $width): self
65 | {
66 | $this->width = $width;
67 | return $this;
68 | }
69 |
70 | public function getType(): string
71 | {
72 | return static::TYPE_IMAGE;
73 | }
74 |
75 | public function toArray(): array
76 | {
77 | return $this->toMergedArray([
78 | 'image' => [
79 | 'url' => $this->url,
80 | 'proxy_url' => $this->proxyUrl,
81 | 'height' => $this->height,
82 | 'width' => $this->width,
83 | ],
84 | ]);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Support/Embeds/ProviderEmbed.php:
--------------------------------------------------------------------------------
1 | name = $name;
36 | return $this;
37 | }
38 |
39 | /**
40 | * URL of provider
41 | *
42 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-provider-structure
43 | * @param string $url
44 | * @return $this
45 | */
46 | public function withUrl(string $url): self
47 | {
48 | $this->url = $url;
49 | return $this;
50 | }
51 |
52 | public function getType(): string
53 | {
54 | return static::TYPE_PROVIDER;
55 | }
56 |
57 | public function toArray(): array
58 | {
59 | return $this->toMergedArray([
60 | 'provider' => [
61 | 'name' => $this->name,
62 | 'url' => $this->url,
63 | ],
64 | ]);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Support/Embeds/ThumbnailEmbed.php:
--------------------------------------------------------------------------------
1 | url = $url;
29 | }
30 |
31 | /**
32 | * A proxied url of the video
33 | *
34 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-video-structure
35 | * @param string $proxyUrl
36 | * @return $this
37 | */
38 | public function withProxyUrl(string $proxyUrl): self
39 | {
40 | $this->proxyUrl = $proxyUrl;
41 | return $this;
42 | }
43 |
44 | /**
45 | * Height of video
46 | *
47 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-video-structure
48 | * @param int $height
49 | * @return $this
50 | */
51 | public function withHeight(int $height): self
52 | {
53 | $this->height = $height;
54 | return $this;
55 | }
56 |
57 | /**
58 | * Width of video
59 | *
60 | * @see https://discord.com/developers/docs/resources/channel#embed-object-embed-video-structure
61 | * @param int $width
62 | * @return $this
63 | */
64 | public function withWidth(int $width): self
65 | {
66 | $this->width = $width;
67 | return $this;
68 | }
69 |
70 | public function getType(): string
71 | {
72 | return static::TYPE_VIDEO;
73 | }
74 |
75 | public function toArray(): array
76 | {
77 | return $this->toMergedArray([
78 | 'video' => [
79 | 'url' => $this->url,
80 | 'proxy_url' => $this->proxyUrl,
81 | 'height' => $this->height,
82 | 'width' => $this->width,
83 | ],
84 | ]);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Support/Interactions/DiscordInteractionResponse.php:
--------------------------------------------------------------------------------
1 | status = $status;
19 | $this->type = $type;
20 | $this->data = $data;
21 | }
22 |
23 | public function getStatus(): int
24 | {
25 | return $this->status;
26 | }
27 |
28 | public function toArray(): array
29 | {
30 | return array_filter([
31 | 'type' => $this->type,
32 | 'data' => $this->data,
33 | ]);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Support/Interactions/Handlers/ApplicationCommandHandler.php:
--------------------------------------------------------------------------------
1 | defaultBehavior = $defaultBehavior;
24 | $this->dispatcher = $dispatcher;
25 | $this->laravel = $laravel;
26 | }
27 |
28 | public function handle(Request $request): DiscordInteractionResponse
29 | {
30 | if ($response = $this->shouldHandleEventExternally($request)) {
31 | return $response;
32 | }
33 |
34 | switch ($this->defaultBehavior) {
35 | case static::BEHAVIOR_LOAD:
36 | return new DiscordInteractionResponse(static::RESPONSE_TYPE_DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE);
37 | case static::BEHAVIOR_DEFER:
38 | return new DiscordInteractionResponse(static::RESPONSE_TYPE_DEFERRED_UPDATE_MESSAGE);
39 | }
40 |
41 | return new DiscordInteractionResponse(static::RESPONSE_TYPE_DEFERRED_UPDATE_MESSAGE);
42 | }
43 |
44 | protected function shouldHandleEventExternally(Request $request): ?DiscordInteractionResponse
45 | {
46 | return $this->generateResponse(
47 | new ApplicationCommandInteractionEvent($request->json()),
48 | ApplicationCommandInteractionEventListenerContract::class,
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Support/Interactions/Handlers/MessageComponentInteractionHandler.php:
--------------------------------------------------------------------------------
1 | defaultBehavior = $defaultBehavior;
25 | $this->dispatcher = $dispatcher;
26 | $this->laravel = $laravel;
27 | }
28 |
29 | public function handle(Request $request): DiscordInteractionResponse
30 | {
31 | if ($response = $this->shouldHandleEventExternally($request)) {
32 | return $response;
33 | }
34 |
35 | switch ($this->defaultBehavior) {
36 | case static::BEHAVIOR_LOAD:
37 | return new DiscordInteractionResponse(static::RESPONSE_TYPE_DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE);
38 | case static::BEHAVIOR_DEFER:
39 | return new DiscordInteractionResponse(static::RESPONSE_TYPE_DEFERRED_UPDATE_MESSAGE);
40 | }
41 |
42 | return new DiscordInteractionResponse(static::RESPONSE_TYPE_DEFERRED_UPDATE_MESSAGE);
43 | }
44 |
45 | protected function shouldHandleEventExternally(Request $request): ?DiscordInteractionResponse
46 | {
47 | return $this->generateResponse(
48 | new MessageComponentInteractionEvent($request->json()),
49 | MessageComponentInteractionEventListenerContract::class,
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Support/Interactions/Handlers/PingHandler.php:
--------------------------------------------------------------------------------
1 | repliedUser = $mention;
39 | return $this;
40 | }
41 |
42 | /**
43 | * Allows roles to be mentioned in message
44 | *
45 | * @see https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mention-types
46 | * @return $this
47 | */
48 | public function allowRolesMention(): self
49 | {
50 | if (!in_array(static::MENTIONS_ROLES, $this->parse)) {
51 | $this->parse[] = static::MENTIONS_ROLES;
52 | }
53 |
54 | return $this;
55 | }
56 |
57 | /**
58 | * Allows users to be mentioned in message
59 | *
60 | * @see https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mention-types
61 | * @return $this
62 | */
63 | public function allowUsersMention(): self
64 | {
65 | if (!in_array(static::MENTIONS_USERS, $this->parse)) {
66 | $this->parse[] = static::MENTIONS_USERS;
67 | }
68 |
69 | return $this;
70 | }
71 |
72 | /**
73 | * Allows everyone to be mentioned in message
74 | *
75 | * @see https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mention-types
76 | * @return $this
77 | */
78 | public function allowEveryoneMention(): self
79 | {
80 | if (!in_array(static::MENTIONS_EVERYONE, $this->parse)) {
81 | $this->parse[] = static::MENTIONS_EVERYONE;
82 | }
83 |
84 | return $this;
85 | }
86 |
87 | /**
88 | * Allows mentions of specific roles
89 | *
90 | * @see https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mentions-structure
91 | * @param array $roles
92 | * @return $this
93 | */
94 | public function allowMentionsForRoles(array $roles): self
95 | {
96 | $this->roles = $roles;
97 | return $this;
98 | }
99 |
100 | /**
101 | * Allows mentions of specific users
102 | *
103 | * @see https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mentions-structure
104 | * @param array $users
105 | * @return $this
106 | */
107 | public function allowMentionsForUsers(array $users): self
108 | {
109 | $this->users = $users;
110 | return $this;
111 | }
112 |
113 | public function toArray(): array
114 | {
115 | return $this->toMergedArray([
116 | 'parse' => $this->parse,
117 | 'roles' => $this->roles,
118 | 'users' => $this->users,
119 | 'replied_user' => $this->repliedUser,
120 | ]);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Support/Objects/EmojiObject.php:
--------------------------------------------------------------------------------
1 | name = $name;
22 | }
23 |
24 | /**
25 | * Returns a Discord-API compliant emoji object array
26 | *
27 | * @see https://discord.com/developers/docs/resources/emoji#emoji-object-emoji-structure
28 | * @return array
29 | */
30 | public function toArray(): array
31 | {
32 | return $this->toMergedArray([
33 | 'name' => $this->name,
34 | ]);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Support/Objects/SelectOptionObject.php:
--------------------------------------------------------------------------------
1 | label = $label;
29 | $this->value = $value;
30 | }
31 |
32 | /**
33 | * An additional description of the option, max 100 characters
34 | *
35 | * @see https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
36 | * @param string $description
37 | * @return $this
38 | */
39 | public function withDescription(string $description): self
40 | {
41 | $this->description = $description;
42 | return $this;
43 | }
44 |
45 | /**
46 | * Will render this option as selected by default
47 | *
48 | * @see https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
49 | * @param bool $default
50 | * @return $this
51 | */
52 | public function default(bool $default = true): self
53 | {
54 | $this->default = $default;
55 | return $this;
56 | }
57 |
58 | /**
59 | * Returns a Discord-API compliant select option array
60 | *
61 | * @see https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
62 | * @return array
63 | */
64 | public function toArray(): array
65 | {
66 | return $this->toMergedArray($this->mergeEmojiObject([
67 | 'label' => $this->label,
68 | 'value' => $this->value,
69 | 'description' => $this->description,
70 | 'default' => $this->default,
71 | ]));
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Support/SupportObject.php:
--------------------------------------------------------------------------------
1 | arrayFilterRecursive([]);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Support/Traits/ApplicationCommand/HasAutoComplete.php:
--------------------------------------------------------------------------------
1 | autocomplete = $autocomplete;
13 | return $this;
14 | }
15 |
16 | protected function mergeAutocomplete(array $merge): array
17 | {
18 | return array_merge($merge, array_filter([
19 | 'autocomplete' => $this->autocomplete,
20 | ]));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Support/Traits/ApplicationCommand/HasChoices.php:
--------------------------------------------------------------------------------
1 | choices)) {
18 | $this->choices = [];
19 | }
20 |
21 | $this->choices[] = $choice;
22 | return $this;
23 | }
24 |
25 | public function choices(array $choices): self
26 | {
27 | $this->choices = $choices;
28 | return $this;
29 | }
30 |
31 | protected function mergeChoices(array $merge): array
32 | {
33 | if (empty($this->choices)) {
34 | return $merge;
35 | }
36 |
37 | return array_merge($merge, [
38 | 'choices' => array_map(function (OptionChoice $choice): array {
39 | return $this->choiceTransformer($choice);
40 | }, $this->choices)
41 | ]);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Support/Traits/ApplicationCommand/HasOptions.php:
--------------------------------------------------------------------------------
1 | options)) {
18 | $this->options = [];
19 | }
20 |
21 | $this->options[] = $option;
22 | return $this;
23 | }
24 |
25 | protected function mergeOptions(array $merge = []): array
26 | {
27 | if (empty($this->options)) {
28 | return $merge;
29 | }
30 |
31 | return array_merge($merge, array_filter([
32 | 'options' => array_map(function (CommandOption $option): array {
33 | return $option->toArray();
34 | }, $this->options),
35 | ]));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Support/Traits/ApplicationCommand/NoChoiceTransformer.php:
--------------------------------------------------------------------------------
1 | toArray();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Support/Traits/DiscordApiService.php:
--------------------------------------------------------------------------------
1 | apiUrl, $endpoint);
20 |
21 | return $this->httpClient->request($method, $url, [
22 | 'headers' => [
23 | 'Authorization' => sprintf('Bot %s', $this->token),
24 | ],
25 | 'json' => $payload,
26 | ]);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Support/Traits/FiltersRecursive.php:
--------------------------------------------------------------------------------
1 | $value) {
11 | if (is_array($value)) {
12 | $toFilter[$key] = $this->arrayFilterRecursive($value);
13 | }
14 | }
15 |
16 | return array_filter($toFilter);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Support/Traits/HasEmojiObject.php:
--------------------------------------------------------------------------------
1 | emoji = $emoji;
15 | return $this;
16 | }
17 |
18 | protected function mergeEmojiObject(array $mergeWith): array
19 | {
20 | return array_merge($mergeWith, [
21 | 'emoji' => ($this->emoji) ? $this->emoji->toArray() : null,
22 | ]);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Support/Traits/HasInteractionListeners.php:
--------------------------------------------------------------------------------
1 | getStaticVariables();
23 | return $this->laravel->make($attributes['listener']);
24 | }
25 |
26 | protected function getListenersFor(string $eventClass): array
27 | {
28 | $listeners = $this->dispatcher->getListeners($eventClass);
29 | return array_values(array_map(function (\Closure $listener) {
30 | return $this->makeListenerFromClosure($listener);
31 | }, $listeners));
32 | }
33 |
34 | protected function defaultBehaviorResponse(array $listeners, $event): DiscordInteractionResponse
35 | {
36 | $listener = $listeners[0];
37 |
38 | $replyContent = $listener->replyContent($event);
39 | $behavior = $listener->behavior($event);
40 |
41 | $data = null;
42 | if (!empty($replyContent)) {
43 | $data = [
44 | 'content' => $replyContent,
45 | ];
46 | }
47 |
48 | return new DiscordInteractionResponse($behavior, $data);
49 | }
50 |
51 | /**
52 | * Determines if an incoming interaction should be handled by the user's application or by this package directly.
53 | *
54 | * If there are any listeners subscribed to the Message- or App- ComponentInteractionEvent, those should be dispatched.
55 | * Additionally, if there is a listener implementing the contract Message- or App- ComponentInteractionEventListenerContract
56 | * then that listener should be instantiated and have certain methods called to generate a DiscordInteractionResponse.
57 | *
58 | * This essentially facilitates overriding the default behavior of responding to interaction requests.
59 | *
60 | * @return DiscordInteractionResponse|null
61 | */
62 | protected function generateResponse(AbstractInteractionEvent $event, string $listenerClass): ?DiscordInteractionResponse
63 | {
64 | $listeners = $this->getListenersFor(get_class($event));
65 | $listenersImplementingInterface = array_values(array_filter($listeners, function ($listener) use ($listenerClass): bool {
66 | return $listener instanceof $listenerClass;
67 | }));
68 |
69 | if (!empty($listeners)) {
70 | $this->dispatcher->dispatch($event);
71 | }
72 |
73 | if (empty($listenersImplementingInterface)) {
74 | return null;
75 | }
76 |
77 | return $this->defaultBehaviorResponse($listenersImplementingInterface, $event);
78 | }
79 |
80 | protected abstract function shouldHandleEventExternally(Request $request): ?DiscordInteractionResponse;
81 | }
82 |
--------------------------------------------------------------------------------
/src/Support/Traits/MergesArrays.php:
--------------------------------------------------------------------------------
1 | arrayFilterRecursive(array_merge(parent::toArray(), $toMerge));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | addToAssertionCount($container->mockery_getExpectationCount());
15 | }
16 |
17 | \Mockery::close();
18 | }
19 |
20 | parent::tearDown();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Traits/BasicCommandOptionTests.php:
--------------------------------------------------------------------------------
1 | assertSame($this->expectedType, $this->option->getType());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Traits/BasicCommandTests.php:
--------------------------------------------------------------------------------
1 | assertSame($this->expectedType, $this->command->getType());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Unit/Channels/DiscordNotificationChannelTest.php:
--------------------------------------------------------------------------------
1 | discordApiService = \Mockery::mock(DiscordApiServiceContract::class);
26 | $this->channel = new DiscordNotificationChannel($this->discordApiService);
27 | }
28 |
29 | public function testSendPlainText()
30 | {
31 | $notifiable = \Mockery::mock(Notifiable::class);
32 | $notification = \Mockery::mock(DiscordNotificationContract::class);
33 |
34 | $discordNotificationArray = [
35 | 'contentType' => 'plain',
36 | 'channelId' => '12345',
37 | 'message' => 'test message',
38 | ];
39 |
40 | $expectedResponse = [
41 | 'key' => 'value',
42 | ];
43 |
44 | $notification->shouldReceive('toDiscord')
45 | ->once()
46 | ->with($notifiable)
47 | ->andReturn($discordNotificationArray);
48 |
49 | $this->discordApiService->shouldReceive('sendTextMessage')
50 | ->once()
51 | ->with('12345', 'test message', [])
52 | ->andReturn($expectedResponse);
53 |
54 | $result = $this->channel->send($notifiable, $notification);
55 | $this->assertEquals($expectedResponse, $result);
56 | }
57 |
58 | public function testSendRichText()
59 | {
60 | $notifiable = \Mockery::mock(Notifiable::class);
61 | $notification = \Mockery::mock(DiscordNotificationContract::class);
62 |
63 | $embed1 = \Mockery::mock(Embed::class);
64 | $embed2 = \Mockery::mock(Embed::class);
65 |
66 | $component1 = \Mockery::mock(Component::class);
67 | $component2 = \Mockery::mock(Component::class);
68 |
69 | $discordNotificationArray = [
70 | 'contentType' => 'rich',
71 | 'channelId' => '12345',
72 | 'embeds' => [$embed1, $embed2],
73 | 'components' => [$component1, $component2],
74 | ];
75 |
76 | $expectedResponse = [
77 | 'key' => 'value',
78 | ];
79 |
80 | $notification->shouldReceive('toDiscord')
81 | ->once()
82 | ->with($notifiable)
83 | ->andReturn($discordNotificationArray);
84 |
85 | $this->discordApiService->shouldReceive('sendRichTextMessage')
86 | ->once()
87 | ->with('12345', [$embed1, $embed2], [$component1, $component2], [])
88 | ->andReturn($expectedResponse);
89 |
90 | $result = $this->channel->send($notifiable, $notification);
91 | $this->assertEquals($expectedResponse, $result);
92 | }
93 |
94 | public function testSendWithInvalidContentTypeThrowsException()
95 | {
96 | $notifiable = \Mockery::mock(Notifiable::class);
97 | $notification = \Mockery::mock(DiscordNotificationContract::class);
98 |
99 | $discordNotificationArray = [
100 | 'contentType' => 'invalid',
101 | ];
102 |
103 | $notification->shouldReceive('toDiscord')
104 | ->once()
105 | ->with($notifiable)
106 | ->andReturn($discordNotificationArray);
107 |
108 | $this->expectException(\InvalidArgumentException::class);
109 | $this->expectExceptionMessage('invalid is not a valid contentType');
110 |
111 | $this->channel->send($notifiable, $notification);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/tests/Unit/Events/ApplicationCommandInteractionEventTest.php:
--------------------------------------------------------------------------------
1 | assertSame($parameterBag, $event->getInteractionRequest());
19 | }
20 |
21 | public function testGetters()
22 | {
23 | $commandName = 'test-command-name';
24 | $commandId = 'test-command-id';
25 | $channelId = 'test-channel-id';
26 | $applicationId = 'test-app-id';
27 | $type = 42;
28 |
29 | $data = [
30 | 'name' => $commandName,
31 | 'id' => $commandId,
32 | 'type' => $type,
33 | ];
34 |
35 | $parameterBag = new ParameterBag([
36 | 'application_id' => $applicationId,
37 | 'channel_id' => $channelId,
38 | 'data' => $data,
39 | ]);
40 |
41 | $event = new ApplicationCommandInteractionEvent($parameterBag);
42 |
43 | $this->assertSame($commandName, $event->getCommandName());
44 | $this->assertSame($commandId, $event->getCommandId());
45 | $this->assertSame($channelId, $event->getChannelId());
46 | $this->assertSame($applicationId, $event->getApplicationId());
47 | $this->assertSame($type, $event->getCommandType());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Unit/Events/MessageComponentInteractionEventTest.php:
--------------------------------------------------------------------------------
1 | assertSame($parameterBag, $event->getInteractionRequest());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Unit/Services/DiscordApiServiceTest.php:
--------------------------------------------------------------------------------
1 | httpClient = \Mockery::mock(ClientInterface::class);
29 | $this->service = new DiscordApiService($this->token, $this->apiUrl, $this->httpClient);
30 | }
31 |
32 | public function testSendTextMessage()
33 | {
34 | $channelId = '12345';
35 | $message = 'test message';
36 |
37 | $expectedRequestPayload = [
38 | 'content' => $message,
39 | ];
40 |
41 | $expectedResponse = [
42 | 'id' => '54321',
43 | ];
44 |
45 | $responseMock = \Mockery::mock(ResponseInterface::class);
46 | $responseMock->shouldAllowMockingMethod('getContents');
47 |
48 | $responseMock->shouldReceive('getBody')->once()->andReturnSelf();
49 | $responseMock->shouldReceive('getContents')->once()->andReturn(json_encode($expectedResponse));
50 |
51 | $this->httpClient->shouldReceive('request')
52 | ->once()
53 | ->with('POST', sprintf('%s/channels/%s/messages', $this->apiUrl, $channelId), [
54 | 'headers' => [
55 | 'Authorization' => 'Bot ' . $this->token,
56 | ],
57 | 'json' => $expectedRequestPayload,
58 | ])
59 | ->andReturn($responseMock);
60 |
61 | $result = $this->service->sendTextMessage($channelId, $message);
62 | $this->assertEquals($expectedResponse, $result);
63 | }
64 |
65 | public function testSendRichTextMessage()
66 | {
67 | $channelId = '12345';
68 |
69 | $expectedEmbed1Array = ['k1' => 'v1'];
70 | $expectedEmbed2Array = ['k2' => 'v2'];
71 |
72 | $expectedComponent1Array = ['k3' => 'v3'];
73 | $expectedComponent2Array = ['k4' => 'v4'];
74 |
75 | $embed1 = \Mockery::mock(Embed::class);
76 | $embed2 = \Mockery::mock(Embed::class);
77 |
78 | $component1 = \Mockery::mock(Component::class);
79 | $component2 = \Mockery::mock(Component::class);
80 |
81 | $embed1->shouldReceive('toArray')->andReturn($expectedEmbed1Array);
82 | $embed2->shouldReceive('toArray')->andReturn($expectedEmbed2Array);
83 | $component1->shouldReceive('toArray')->andReturn($expectedComponent1Array);
84 | $component2->shouldReceive('toArray')->andReturn($expectedComponent2Array);
85 |
86 | $expectedRequestPayload = [
87 | 'embeds' => [$expectedEmbed1Array, $expectedEmbed2Array],
88 | 'components' => [$expectedComponent1Array, $expectedComponent2Array],
89 | ];
90 |
91 | $expectedResponse = [
92 | 'id' => '54321',
93 | ];
94 |
95 | $responseMock = \Mockery::mock(ResponseInterface::class);
96 | $responseMock->shouldAllowMockingMethod('getContents');
97 |
98 | $responseMock->shouldReceive('getBody')->andReturnSelf();
99 | $responseMock->shouldReceive('getContents')->andReturn(json_encode($expectedResponse));
100 |
101 | $this->httpClient->shouldReceive('request')
102 | ->once()
103 | ->with('POST', sprintf('%s/channels/%s/messages', $this->apiUrl, $channelId), [
104 | 'headers' => [
105 | 'Authorization' => 'Bot ' . $this->token,
106 | ],
107 | 'json' => $expectedRequestPayload,
108 | ])
109 | ->andReturn($responseMock);
110 |
111 | $result = $this->service->sendRichTextMessage($channelId, [$embed1, $embed2], [$component1, $component2]);
112 | $this->assertEquals($expectedResponse, $result);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/tests/Unit/Services/DiscordInteractionServiceTest.php:
--------------------------------------------------------------------------------
1 | publicKey = sodium_crypto_sign_publickey($keypair);
37 | $this->privateKey = sodium_crypto_sign_secretkey($keypair);
38 |
39 | $this->laravel = \Mockery::mock(Application::class);
40 |
41 | $this->service = new DiscordInteractionService($this->applicationId, bin2hex($this->publicKey), $this->laravel);
42 | }
43 |
44 | public function testHandleInteractionRequestThrows404WhenNoHandlerFound()
45 | {
46 | $timestamp = '12345';
47 | $body = 'test-body';
48 | $signature = bin2hex(sodium_crypto_sign_detached(sprintf('%s%s', $timestamp, $body), $this->privateKey));
49 |
50 | $expectedJson = [
51 | 'type' => 999, // invalid type
52 | ];
53 |
54 | $request = \Mockery::mock(Request::class);
55 | $request->shouldAllowMockingMethod('all');
56 |
57 | $request->shouldReceive('json')->once()->andReturnSelf();
58 | $request->shouldReceive('all')->once()->andReturn($expectedJson);
59 |
60 | $request->shouldReceive('header')->once()->with('X-Signature-Ed25519')->andReturn($signature);
61 | $request->shouldReceive('header')->once()->with('X-Signature-Timestamp')->andReturn($timestamp);
62 | $request->shouldReceive('getContent')->once()->andReturn($body);
63 |
64 | $this->expectException(NotFoundHttpException::class);
65 | $this->service->handleInteractionRequest($request);
66 | }
67 |
68 | public function testHandleInteractionRequestThrows401WhenMissingRequiredData()
69 | {
70 | $request = \Mockery::mock(Request::class);
71 | $request->shouldAllowMockingMethod('all');
72 |
73 | $request->shouldReceive('header')->once()->with('X-Signature-Ed25519')->andReturnNull();
74 | $request->shouldReceive('header')->once()->with('X-Signature-Timestamp')->andReturnNull();
75 | $request->shouldReceive('getContent')->once()->andReturnNull();
76 |
77 | $this->expectException(UnauthorizedHttpException::class);
78 | $this->service->handleInteractionRequest($request);
79 | }
80 |
81 | public function testHandleInteractionRequestThrows401WhenSignatureVerificationFails()
82 | {
83 | $timestamp = '12345';
84 | $body = 'test-body';
85 | $signature = bin2hex(sodium_crypto_sign_detached(sprintf('%s%s', $timestamp, $body), $this->privateKey));
86 |
87 | $request = \Mockery::mock(Request::class);
88 | $request->shouldAllowMockingMethod('all');
89 |
90 | $request->shouldReceive('header')->once()->with('X-Signature-Ed25519')->andReturn($signature);
91 | $request->shouldReceive('header')->once()->with('X-Signature-Timestamp')->andReturn($timestamp);
92 | $request->shouldReceive('getContent')->once()->andReturn('something different');
93 |
94 | $this->expectException(UnauthorizedHttpException::class);
95 | $this->service->handleInteractionRequest($request);
96 | }
97 |
98 | public function testHandleInteractionRequestThrows401OnSodiumException()
99 | {
100 | $timestamp = '12345';
101 | $body = 'test-body';
102 | $signature = bin2hex(sodium_crypto_sign_detached(sprintf('%s%s', $timestamp, $body), $this->privateKey));
103 |
104 | $request = \Mockery::mock(Request::class);
105 | $request->shouldAllowMockingMethod('all');
106 |
107 | $request->shouldReceive('header')->once()->with('X-Signature-Ed25519')->andReturn($signature);
108 | $request->shouldReceive('header')->once()->with('X-Signature-Timestamp')->andReturn($timestamp);
109 | $request->shouldReceive('getContent')->once()->andReturn('something different');
110 |
111 | $this->expectException(UnauthorizedHttpException::class);
112 |
113 | $service = new DiscordInteractionService($this->applicationId, 'abc123', $this->laravel);
114 | $service->handleInteractionRequest($request);
115 | }
116 |
117 | /**
118 | * @dataProvider handlerDataProvider
119 | */
120 | public function testHandleInteractionBuildsAndHandlesRequest(int $type, string $expectedHandlerClass)
121 | {
122 | $timestamp = '12345';
123 | $body = 'test-body';
124 | $signature = bin2hex(sodium_crypto_sign_detached(sprintf('%s%s', $timestamp, $body), $this->privateKey));
125 |
126 | $expectedJson = [
127 | 'type' => $type,
128 | ];
129 |
130 | $request = \Mockery::mock(Request::class);
131 | $request->shouldAllowMockingMethod('all');
132 |
133 | $request->shouldReceive('json')->once()->andReturnSelf();
134 | $request->shouldReceive('all')->once()->andReturn($expectedJson);
135 |
136 | $request->shouldReceive('header')->once()->with('X-Signature-Ed25519')->andReturn($signature);
137 | $request->shouldReceive('header')->once()->with('X-Signature-Timestamp')->andReturn($timestamp);
138 | $request->shouldReceive('getContent')->once()->andReturn($body);
139 |
140 | $expectedHandlerReturn = new DiscordInteractionResponse(0);
141 |
142 | $handlerMock = \Mockery::mock(InteractionHandler::class);
143 | $handlerMock->shouldReceive('handle')->once()->with($request)->andReturn($expectedHandlerReturn);
144 |
145 | $this->laravel->shouldReceive('make')->once()->with($expectedHandlerClass)->andReturn($handlerMock);
146 |
147 | $result = $this->service->handleInteractionRequest($request);
148 | $this->assertSame($expectedHandlerReturn, $result);
149 | }
150 |
151 | public function handlerDataProvider(): array
152 | {
153 | return [
154 | [1, PingHandler::class],
155 | [2, ApplicationCommandHandler::class],
156 | [3, MessageComponentInteractionHandler::class],
157 | ];
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Builder/ComponentBuilderTest.php:
--------------------------------------------------------------------------------
1 | 'v1'];
21 | $expectedComponent2Array = ['k2' => 'v2'];
22 |
23 | $component1 = \Mockery::mock(Component::class);
24 | $component2 = \Mockery::mock(Component::class);
25 |
26 | $component1->shouldReceive('toArray')->andReturn($expectedComponent1Array);
27 | $component2->shouldReceive('toArray')->andReturn($expectedComponent2Array);
28 |
29 | $builder->addComponent($component1)->addComponent($component2);
30 |
31 | $actionRow = $builder->getActionRow();
32 |
33 | $this->assertInstanceOf(ActionRow::class, $actionRow);
34 | $this->assertEquals([
35 | 'type' => Component::TYPE_ACTION_ROW,
36 | 'components' => [$expectedComponent1Array, $expectedComponent2Array],
37 | ], $actionRow->toArray());
38 | }
39 |
40 | public function testWithSelectMenuObject()
41 | {
42 | $builder = new ComponentBuilder();
43 |
44 | $label = 'test label';
45 | $value = 'test value';
46 |
47 | $option = $builder->withSelectOptionObject($label, $value);
48 |
49 | $this->assertInstanceOf(SelectOptionObject::class, $option);
50 | $this->assertEquals([
51 | 'label' => $label,
52 | 'value' => $value,
53 | ], $option->toArray());
54 | }
55 |
56 | public function testAddActionButton()
57 | {
58 | $builder = new ComponentBuilder();
59 |
60 | $label = 'test label';
61 | $customId = 'custom-id';
62 |
63 | $builder->addActionButton($label, $customId);
64 | $actionRow = $builder->getActionRow();
65 |
66 | $this->assertEquals([
67 | 'type' => Component::TYPE_ACTION_ROW,
68 | 'components' => [
69 | [
70 | 'type' => Component::TYPE_BUTTON,
71 | 'custom_id' => $customId,
72 | 'style' => GenericButtonComponent::STYLE_PRIMARY,
73 | 'label' => $label,
74 | ],
75 | ],
76 | ], $actionRow->toArray());
77 | }
78 |
79 | public function testAddLinkButton()
80 | {
81 | $builder = new ComponentBuilder();
82 |
83 | $label = 'test label';
84 | $url = 'test url';
85 |
86 | $builder->addLinkButton($label, $url);
87 | $actionRow = $builder->getActionRow();
88 |
89 | $this->assertEquals([
90 | 'type' => Component::TYPE_ACTION_ROW,
91 | 'components' => [
92 | [
93 | 'type' => Component::TYPE_BUTTON,
94 | 'url' => $url,
95 | 'style' => GenericButtonComponent::STYLE_LINK,
96 | 'label' => $label,
97 | ],
98 | ],
99 | ], $actionRow->toArray());
100 | }
101 |
102 | public function testAddSelectMenuComponent()
103 | {
104 | $builder = new ComponentBuilder();
105 |
106 | $customId = 'custom-id';
107 | $builder->addSelectMenuComponent([
108 | $builder->withSelectOptionObject('option1', 'value1'),
109 | $builder->withSelectOptionObject('option2', 'value2'),
110 | ], $customId);
111 |
112 | $actionRow = $builder->getActionRow();
113 | $this->assertEquals([
114 | 'type' => Component::TYPE_ACTION_ROW,
115 | 'components' => [
116 | [
117 | 'type' => Component::TYPE_SELECT_MENU,
118 | 'custom_id' => $customId,
119 | 'options' => [
120 | [
121 | 'label' => 'option1',
122 | 'value' => 'value1',
123 | ],
124 | [
125 | 'label' => 'option2',
126 | 'value' => 'value2',
127 | ]
128 | ],
129 | ],
130 | ],
131 | ], $actionRow->toArray());
132 | }
133 |
134 | public function testAddShortTextInput()
135 | {
136 | $builder = new ComponentBuilder();
137 |
138 | $label = 'test label';
139 | $customId = 'custom-id';
140 |
141 | $builder->addShortTextInput($label, $customId);
142 | $actionButton = $builder->getActionRow();
143 |
144 | $this->assertEquals([
145 | 'type' => Component::TYPE_ACTION_ROW,
146 | 'components' => [
147 | [
148 | 'type' => Component::TYPE_TEXT_INPUT,
149 | 'custom_id' => $customId,
150 | 'style' => GenericTextInputComponent::STYLE_SHORT,
151 | 'label' => $label,
152 | ],
153 | ],
154 | ], $actionButton->toArray());
155 | }
156 |
157 | public function testParagraphTextInput()
158 | {
159 | $builder = new ComponentBuilder();
160 |
161 | $label = 'test label';
162 | $customId = 'custom-id';
163 |
164 | $builder->addParagraphTextInput($label, $customId);
165 | $actionButton = $builder->getActionRow();
166 |
167 | $this->assertEquals([
168 | 'type' => Component::TYPE_ACTION_ROW,
169 | 'components' => [
170 | [
171 | 'type' => Component::TYPE_TEXT_INPUT,
172 | 'custom_id' => $customId,
173 | 'style' => GenericTextInputComponent::STYLE_PARAGRAPH,
174 | 'label' => $label,
175 | ],
176 | ],
177 | ], $actionButton->toArray());
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Builder/EmbedBuilderTest.php:
--------------------------------------------------------------------------------
1 | 'v1'];
17 | $expectedEmbed2Array = ['k2' => 'v2'];
18 |
19 | $embed1 = \Mockery::mock(Embed::class);
20 | $embed2 = \Mockery::mock(Embed::class);
21 |
22 | $embed1->shouldReceive('toArray')->andReturn($expectedEmbed1Array);
23 | $embed2->shouldReceive('toArray')->andReturn($expectedEmbed2Array);
24 |
25 | $builder->addEmbed($embed1)->addEmbed($embed2);
26 |
27 | $this->assertSame([$embed1, $embed2], $builder->getEmbeds());
28 | $this->assertEquals([
29 | $expectedEmbed1Array,
30 | $expectedEmbed2Array,
31 | ], $builder->toArray());
32 | }
33 |
34 | public function testAddFooter()
35 | {
36 | $footerText = 'test text';
37 |
38 | $builder = new EmbedBuilder();
39 | $builder->addFooter($footerText);
40 |
41 | $this->assertEquals([
42 | [
43 | 'type' => Embed::TYPE_FOOTER,
44 | 'footer' => [
45 | 'text' => $footerText,
46 | ],
47 | ]
48 | ], $builder->toArray());
49 | }
50 |
51 | public function testAddImage()
52 | {
53 | $imageUrl = 'test url';
54 |
55 | $builder = new EmbedBuilder();
56 | $builder->addImage($imageUrl);
57 |
58 | $this->assertEquals([
59 | [
60 | 'type' => Embed::TYPE_IMAGE,
61 | 'image' => [
62 | 'url' => $imageUrl,
63 | ],
64 | ]
65 | ], $builder->toArray());
66 | }
67 |
68 | public function testAddThumbnail()
69 | {
70 | $imageUrl = 'test url';
71 |
72 | $builder = new EmbedBuilder();
73 | $builder->addThumbnail($imageUrl);
74 |
75 | $this->assertEquals([
76 | [
77 | 'type' => Embed::TYPE_THUMBNAIL,
78 | 'image' => [
79 | 'url' => $imageUrl,
80 | ],
81 | ]
82 | ], $builder->toArray());
83 | }
84 |
85 | public function testAddVideo()
86 | {
87 | $videoUrl = 'test url';
88 |
89 | $builder = new EmbedBuilder();
90 | $builder->addVideo($videoUrl);
91 |
92 | $this->assertEquals([
93 | [
94 | 'type' => Embed::TYPE_VIDEO,
95 | 'video' => [
96 | 'url' => $videoUrl,
97 | ],
98 | ]
99 | ], $builder->toArray());
100 | }
101 |
102 | public function testAddProvider()
103 | {
104 | $name = 'test name';
105 | $url = 'test url';
106 |
107 | $builder = new EmbedBuilder();
108 | $builder->addProvider($name, $url);
109 |
110 | $this->assertEquals([
111 | [
112 | 'type' => Embed::TYPE_PROVIDER,
113 | 'provider' => [
114 | 'name' => $name,
115 | 'url' => $url,
116 | ],
117 | ]
118 | ], $builder->toArray());
119 | }
120 |
121 | public function testAddAuthor()
122 | {
123 | $authorName = 'test name';
124 |
125 | $builder = new EmbedBuilder();
126 | $builder->addAuthor($authorName);
127 |
128 | $this->assertEquals([
129 | [
130 | 'type' => Embed::TYPE_AUTHOR,
131 | 'author' => [
132 | 'name' => $authorName,
133 | ],
134 | ]
135 | ], $builder->toArray());
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/MessageCommandTest.php:
--------------------------------------------------------------------------------
1 | expectedType = Command::TYPE_MESSAGE;
20 | $this->command = new MessageCommand('test');
21 | }
22 |
23 | public function testToArray()
24 | {
25 | $name = 'test-name';
26 | $command = new MessageCommand($name);
27 |
28 | $result = $command->toArray();
29 | $this->assertEquals([
30 | 'type' => Command::TYPE_MESSAGE,
31 | 'name' => $name,
32 | ], $result);
33 | }
34 |
35 | public function testToArrayWithOptions()
36 | {
37 | $name = 'test-name';
38 | $version = 'test-version';
39 | $defaultMemPermissions = 'test-perm';
40 | $parentAppId = 'test-app-id';
41 |
42 | $nameLocal = ['en-us'];
43 | $descLocal = ['es'];
44 |
45 | $command = new MessageCommand($name);
46 |
47 | $command->version($version);
48 | $command->dmPermission();
49 | $command->defaultPermission();
50 | $command->defaultMemberPermissions($defaultMemPermissions);
51 | $command->nsfw();
52 | $command->parentApplication($parentAppId);
53 |
54 | $command->nameLocalizations($nameLocal);
55 | $command->descriptionLocalizations($descLocal);
56 |
57 | $result = $command->toArray();
58 | $this->assertEquals([
59 | 'type' => Command::TYPE_MESSAGE,
60 | 'name' => $name,
61 | 'application_id' => $parentAppId,
62 | 'default_member_permissions' => $defaultMemPermissions,
63 | 'dm_permission' => true,
64 | 'default_permission' => true,
65 | 'nsfw' => true,
66 | 'version' => $version,
67 | 'name_localizations' => $nameLocal,
68 | 'description_localizations' => $descLocal,
69 | ], $result);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/AttachmentOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_ATTACHMENT;
20 | $this->option = new AttachmentOption('option', 'desc');
21 | }
22 |
23 | public function testToArray()
24 | {
25 | $name = 'test-name';
26 | $description = 'test-desc';
27 |
28 | $option = new AttachmentOption($name, $description);
29 |
30 | $this->assertEquals([
31 | 'type' => CommandOption::TYPE_ATTACHMENT,
32 | 'name' => $name,
33 | 'description' => $description,
34 | ], $option->toArray());
35 | }
36 |
37 | public function testToArrayWithOptions()
38 | {
39 | $name = 'test-name';
40 | $description = 'test-desc';
41 |
42 | $option = new AttachmentOption($name, $description);
43 |
44 | $option->required()
45 | ->nameLocalizations(['l1'])
46 | ->descriptionLocalizations(['l2']);
47 |
48 | $this->assertEquals([
49 | 'type' => CommandOption::TYPE_ATTACHMENT,
50 | 'name' => $name,
51 | 'description' => $description,
52 | 'required' => true,
53 | 'name_localizations' => ['l1'],
54 | 'description_localizations' => ['l2'],
55 | ], $option->toArray());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/BooleanOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_BOOLEAN;
20 | $this->option = new BooleanOption('option', 'desc');
21 | }
22 |
23 | public function testToArray()
24 | {
25 | $name = 'test-name';
26 | $description = 'test-desc';
27 |
28 | $option = new BooleanOption($name, $description);
29 |
30 | $this->assertEquals([
31 | 'type' => CommandOption::TYPE_BOOLEAN,
32 | 'name' => $name,
33 | 'description' => $description,
34 | ], $option->toArray());
35 | }
36 |
37 | public function testToArrayWithOptions()
38 | {
39 | $name = 'test-name';
40 | $description = 'test-desc';
41 |
42 | $option = new BooleanOption($name, $description);
43 |
44 | $option->required()
45 | ->nameLocalizations(['l1'])
46 | ->descriptionLocalizations(['l2']);
47 |
48 | $this->assertEquals([
49 | 'type' => CommandOption::TYPE_BOOLEAN,
50 | 'name' => $name,
51 | 'description' => $description,
52 | 'required' => true,
53 | 'name_localizations' => ['l1'],
54 | 'description_localizations' => ['l2'],
55 | ], $option->toArray());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/ChannelOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_CHANNEL;
20 | $this->option = new ChannelOption('option', 'desc');
21 | }
22 |
23 | public function testToArray()
24 | {
25 | $name = 'test-name';
26 | $description = 'test-desc';
27 |
28 | $option = new ChannelOption($name, $description);
29 |
30 | $this->assertEquals([
31 | 'type' => CommandOption::TYPE_CHANNEL,
32 | 'name' => $name,
33 | 'description' => $description,
34 | ], $option->toArray());
35 | }
36 |
37 | public function testToArrayWithOptions()
38 | {
39 | $name = 'test-name';
40 | $description = 'test-desc';
41 |
42 | $option = new ChannelOption($name, $description);
43 |
44 | $option->required()
45 | ->nameLocalizations(['l1'])
46 | ->descriptionLocalizations(['l2'])
47 | ->channelTypes(['test']);
48 |
49 | $this->assertEquals([
50 | 'type' => CommandOption::TYPE_CHANNEL,
51 | 'name' => $name,
52 | 'description' => $description,
53 | 'required' => true,
54 | 'name_localizations' => ['l1'],
55 | 'description_localizations' => ['l2'],
56 | 'channel_types' => ['test'],
57 | ], $option->toArray());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/IntegerOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_INTEGER;
21 | $this->option = new IntegerOption('option', 'desc');
22 | }
23 |
24 | public function testToArray()
25 | {
26 | $name = 'test-name';
27 | $description = 'test-desc';
28 |
29 | $option = new IntegerOption($name, $description);
30 |
31 | $this->assertEquals([
32 | 'type' => CommandOption::TYPE_INTEGER,
33 | 'name' => $name,
34 | 'description' => $description,
35 | ], $option->toArray());
36 | }
37 |
38 | public function testToArrayWithOptions()
39 | {
40 | $name = 'test-name';
41 | $description = 'test-desc';
42 |
43 | $option = new IntegerOption($name, $description);
44 |
45 | $option->required()
46 | ->nameLocalizations(['l1'])
47 | ->descriptionLocalizations(['l2'])
48 | ->minValue(42)
49 | ->maxValue(142);
50 |
51 | $this->assertEquals([
52 | 'type' => CommandOption::TYPE_INTEGER,
53 | 'name' => $name,
54 | 'description' => $description,
55 | 'required' => true,
56 | 'name_localizations' => ['l1'],
57 | 'description_localizations' => ['l2'],
58 | 'min_value' => 42,
59 | 'max_value' => 142,
60 | ], $option->toArray());
61 | }
62 |
63 | public function testToArrayWithChoices()
64 | {
65 | $name = 'test-name';
66 | $description = 'test-desc';
67 |
68 | $option = new IntegerOption($name, $description);
69 |
70 | $choice1 = new OptionChoice('choice1', '42');
71 | $choice2 = new OptionChoice('choice2', '52');
72 | $choice3 = new OptionChoice('choice3', '62');
73 |
74 | $option->choices([$choice1, $choice2, $choice3]);
75 |
76 | $this->assertEquals([
77 | 'type' => CommandOption::TYPE_INTEGER,
78 | 'name' => $name,
79 | 'description' => $description,
80 | 'choices' => [
81 | [
82 | 'name' => 'choice1',
83 | 'value' => 42,
84 | ],
85 | [
86 | 'name' => 'choice2',
87 | 'value' => 52,
88 | ],
89 | [
90 | 'name' => 'choice3',
91 | 'value' => 62,
92 | ],
93 | ],
94 | ], $option->toArray());
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/MentionableOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_MENTIONABLE;
20 | $this->option = new MentionableOption('option', 'desc');
21 | }
22 |
23 | public function testToArray()
24 | {
25 | $name = 'test-name';
26 | $description = 'test-desc';
27 |
28 | $option = new MentionableOption($name, $description);
29 |
30 | $this->assertEquals([
31 | 'type' => CommandOption::TYPE_MENTIONABLE,
32 | 'name' => $name,
33 | 'description' => $description,
34 | ], $option->toArray());
35 | }
36 |
37 | public function testToArrayWithOptions()
38 | {
39 | $name = 'test-name';
40 | $description = 'test-desc';
41 |
42 | $option = new MentionableOption($name, $description);
43 |
44 | $option->required()
45 | ->nameLocalizations(['l1'])
46 | ->descriptionLocalizations(['l2']);
47 |
48 | $this->assertEquals([
49 | 'type' => CommandOption::TYPE_MENTIONABLE,
50 | 'name' => $name,
51 | 'description' => $description,
52 | 'required' => true,
53 | 'name_localizations' => ['l1'],
54 | 'description_localizations' => ['l2'],
55 | ], $option->toArray());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/NumberOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_NUMBER;
22 | $this->option = new NumberOption('option', 'desc');
23 | }
24 |
25 | public function testToArray()
26 | {
27 | $name = 'test-name';
28 | $description = 'test-desc';
29 |
30 | $option = new NumberOption($name, $description);
31 |
32 | $this->assertEquals([
33 | 'type' => CommandOption::TYPE_NUMBER,
34 | 'name' => $name,
35 | 'description' => $description,
36 | ], $option->toArray());
37 | }
38 |
39 | public function testToArrayWithOptions()
40 | {
41 | $name = 'test-name';
42 | $description = 'test-desc';
43 |
44 | $option = new NumberOption($name, $description);
45 |
46 | $option->required()
47 | ->nameLocalizations(['l1'])
48 | ->descriptionLocalizations(['l2'])
49 | ->minValue(4.2)
50 | ->maxValue(142);
51 |
52 | $this->assertEquals([
53 | 'type' => CommandOption::TYPE_NUMBER,
54 | 'name' => $name,
55 | 'description' => $description,
56 | 'required' => true,
57 | 'name_localizations' => ['l1'],
58 | 'description_localizations' => ['l2'],
59 | 'min_value' => 4.2,
60 | 'max_value' => 142,
61 | ], $option->toArray());
62 | }
63 |
64 | public function testToArrayWithChoices()
65 | {
66 | $name = 'test-name';
67 | $description = 'test-desc';
68 |
69 | $option = new NumberOption($name, $description);
70 |
71 | $choice1 = new OptionChoice('choice1', '4.2');
72 | $choice2 = new OptionChoice('choice2', '52');
73 | $choice3 = new OptionChoice('choice3', '6.2');
74 |
75 | $option->choices([$choice1, $choice2, $choice3]);
76 |
77 | $this->assertEquals([
78 | 'type' => CommandOption::TYPE_NUMBER,
79 | 'name' => $name,
80 | 'description' => $description,
81 | 'choices' => [
82 | [
83 | 'name' => 'choice1',
84 | 'value' => 4.2,
85 | ],
86 | [
87 | 'name' => 'choice2',
88 | 'value' => 52,
89 | ],
90 | [
91 | 'name' => 'choice3',
92 | 'value' => 6.2,
93 | ],
94 | ],
95 | ], $option->toArray());
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/OptionChoiceTest.php:
--------------------------------------------------------------------------------
1 | nameLocalizations(['l1']);
16 |
17 | $this->assertEquals([
18 | 'name' => 'test-name',
19 | 'value' => 'test-value',
20 | 'name_localizations' => ['l1'],
21 | ], $choice->toArray());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/RoleOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_ROLE;
20 | $this->option = new RoleOption('option', 'desc');
21 | }
22 |
23 | public function testToArray()
24 | {
25 | $name = 'test-name';
26 | $description = 'test-desc';
27 |
28 | $option = new RoleOption($name, $description);
29 |
30 | $this->assertEquals([
31 | 'type' => CommandOption::TYPE_ROLE,
32 | 'name' => $name,
33 | 'description' => $description,
34 | ], $option->toArray());
35 | }
36 |
37 | public function testToArrayWithOptions()
38 | {
39 | $name = 'test-name';
40 | $description = 'test-desc';
41 |
42 | $option = new RoleOption($name, $description);
43 |
44 | $option->required()
45 | ->nameLocalizations(['l1'])
46 | ->descriptionLocalizations(['l2']);
47 |
48 | $this->assertEquals([
49 | 'type' => CommandOption::TYPE_ROLE,
50 | 'name' => $name,
51 | 'description' => $description,
52 | 'required' => true,
53 | 'name_localizations' => ['l1'],
54 | 'description_localizations' => ['l2'],
55 | ], $option->toArray());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/StringOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_STRING;
21 | $this->option = new StringOption('option', 'desc');
22 | }
23 |
24 | public function testToArray()
25 | {
26 | $name = 'test-name';
27 | $description = 'test-desc';
28 |
29 | $option = new StringOption($name, $description);
30 |
31 | $this->assertEquals([
32 | 'type' => CommandOption::TYPE_STRING,
33 | 'name' => $name,
34 | 'description' => $description,
35 | ], $option->toArray());
36 | }
37 |
38 | public function testToArrayWithOptions()
39 | {
40 | $name = 'test-name';
41 | $description = 'test-desc';
42 |
43 | $option = new StringOption($name, $description);
44 |
45 | $option->required()
46 | ->nameLocalizations(['l1'])
47 | ->descriptionLocalizations(['l2'])
48 | ->maxLength(123)
49 | ->autocomplete();
50 |
51 | $this->assertEquals([
52 | 'type' => CommandOption::TYPE_STRING,
53 | 'name' => $name,
54 | 'description' => $description,
55 | 'required' => true,
56 | 'name_localizations' => ['l1'],
57 | 'description_localizations' => ['l2'],
58 | 'max_length' => 123,
59 | 'autocomplete' => true,
60 | ], $option->toArray());
61 | }
62 |
63 | public function testToArrayWithChoices()
64 | {
65 | $name = 'test-name';
66 | $description = 'test-desc';
67 |
68 | $option = new StringOption($name, $description);
69 |
70 | $choice1 = new OptionChoice('choice1', 'test');
71 | $choice2 = new OptionChoice('choice2', 'test2');
72 | $choice3 = new OptionChoice('choice3', 'test3');
73 |
74 | $option->choice($choice1);
75 | $option->choice($choice2);
76 | $option->choice($choice3);
77 |
78 | $this->assertEquals([
79 | 'type' => CommandOption::TYPE_STRING,
80 | 'name' => $name,
81 | 'description' => $description,
82 | 'choices' => [
83 | [
84 | 'name' => 'choice1',
85 | 'value' => 'test',
86 | ],
87 | [
88 | 'name' => 'choice2',
89 | 'value' => 'test2',
90 | ],
91 | [
92 | 'name' => 'choice3',
93 | 'value' => 'test3',
94 | ],
95 | ],
96 | ], $option->toArray());
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/SubCommandGroupOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_SUB_COMMAND_GROUP;
20 | $this->option = new SubCommandGroupOption('option', 'desc');
21 | }
22 |
23 | public function testToArray()
24 | {
25 | $name = 'test-name';
26 | $description = 'test-desc';
27 |
28 | $option = new SubCommandGroupOption($name, $description);
29 |
30 | $this->assertEquals([
31 | 'type' => CommandOption::TYPE_SUB_COMMAND_GROUP,
32 | 'name' => $name,
33 | 'description' => $description,
34 | ], $option->toArray());
35 | }
36 |
37 | public function testToArrayWithOptions()
38 | {
39 | $name = 'test-name';
40 | $description = 'test-desc';
41 |
42 | $option = new SubCommandGroupOption($name, $description);
43 |
44 | $option->required()
45 | ->nameLocalizations(['l1'])
46 | ->descriptionLocalizations(['l2']);
47 |
48 | $this->assertEquals([
49 | 'type' => CommandOption::TYPE_SUB_COMMAND_GROUP,
50 | 'name' => $name,
51 | 'description' => $description,
52 | 'required' => true,
53 | 'name_localizations' => ['l1'],
54 | 'description_localizations' => ['l2'],
55 | ], $option->toArray());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/SubCommandOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_SUB_COMMAND;
20 | $this->option = new SubCommandOption('option', 'desc');
21 | }
22 |
23 | public function testToArray()
24 | {
25 | $name = 'test-name';
26 | $description = 'test-desc';
27 |
28 | $option = new SubCommandOption($name, $description);
29 |
30 | $this->assertEquals([
31 | 'type' => CommandOption::TYPE_SUB_COMMAND,
32 | 'name' => $name,
33 | 'description' => $description,
34 | ], $option->toArray());
35 | }
36 |
37 | public function testToArrayWithOptions()
38 | {
39 | $name = 'test-name';
40 | $description = 'test-desc';
41 |
42 | $option = new SubCommandOption($name, $description);
43 |
44 | $option->required()
45 | ->nameLocalizations(['l1'])
46 | ->descriptionLocalizations(['l2']);
47 |
48 | $this->assertEquals([
49 | 'type' => CommandOption::TYPE_SUB_COMMAND,
50 | 'name' => $name,
51 | 'description' => $description,
52 | 'required' => true,
53 | 'name_localizations' => ['l1'],
54 | 'description_localizations' => ['l2'],
55 | ], $option->toArray());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/Options/UserOptionTest.php:
--------------------------------------------------------------------------------
1 | expectedType = CommandOption::TYPE_USER;
20 | $this->option = new UserOption('option', 'desc');
21 | }
22 |
23 | public function testToArray()
24 | {
25 | $name = 'test-name';
26 | $description = 'test-desc';
27 |
28 | $option = new UserOption($name, $description);
29 |
30 | $this->assertEquals([
31 | 'type' => CommandOption::TYPE_USER,
32 | 'name' => $name,
33 | 'description' => $description,
34 | ], $option->toArray());
35 | }
36 |
37 | public function testToArrayWithOptions()
38 | {
39 | $name = 'test-name';
40 | $description = 'test-desc';
41 |
42 | $option = new UserOption($name, $description);
43 |
44 | $option->required()
45 | ->nameLocalizations(['l1'])
46 | ->descriptionLocalizations(['l2']);
47 |
48 | $this->assertEquals([
49 | 'type' => CommandOption::TYPE_USER,
50 | 'name' => $name,
51 | 'description' => $description,
52 | 'required' => true,
53 | 'name_localizations' => ['l1'],
54 | 'description_localizations' => ['l2'],
55 | ], $option->toArray());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/SlashCommandTest.php:
--------------------------------------------------------------------------------
1 | expectedType = Command::TYPE_CHAT_INPUT;
22 | $this->command = new SlashCommand('test', 'desc');
23 | }
24 |
25 | public function testToArray()
26 | {
27 | $name = 'test-name';
28 | $description = 'test description';
29 |
30 | $command = new SlashCommand($name, $description);
31 |
32 | $result = $command->toArray();
33 | $this->assertEquals([
34 | 'type' => Command::TYPE_CHAT_INPUT,
35 | 'name' => $name,
36 | 'description' => $description,
37 | ], $result);
38 | }
39 |
40 | public function testToArrayWithOptions()
41 | {
42 | $name = 'test-name';
43 | $description = 'test description';
44 | $version = 'test-version';
45 | $defaultMemPermissions = 'test-perm';
46 | $parentAppId = 'test-app-id';
47 |
48 | $nameLocal = ['en-us'];
49 | $descLocal = ['es'];
50 |
51 | $command = new SlashCommand($name, $description);
52 |
53 | $command->version($version);
54 | $command->dmPermission();
55 | $command->defaultPermission();
56 | $command->defaultMemberPermissions($defaultMemPermissions);
57 | $command->nsfw();
58 | $command->parentApplication($parentAppId);
59 |
60 | $command->nameLocalizations($nameLocal);
61 | $command->descriptionLocalizations($descLocal);
62 |
63 | $result = $command->toArray();
64 | $this->assertEquals([
65 | 'type' => Command::TYPE_CHAT_INPUT,
66 | 'name' => $name,
67 | 'description' => $description,
68 | 'application_id' => $parentAppId,
69 | 'default_member_permissions' => $defaultMemPermissions,
70 | 'dm_permission' => true,
71 | 'default_permission' => true,
72 | 'nsfw' => true,
73 | 'version' => $version,
74 | 'name_localizations' => $nameLocal,
75 | 'description_localizations' => $descLocal,
76 | ], $result);
77 | }
78 |
79 | public function testToArrayWithAdditionalOptions()
80 | {
81 | $name = 'test-name';
82 | $description = 'test description';
83 |
84 | $optionArray = [
85 | 'key' => 'value',
86 | ];
87 |
88 | $option2Array = [
89 | 'another' => 'value',
90 | ];
91 |
92 | $command = new SlashCommand($name, $description);
93 |
94 | $option1 = \Mockery::mock(CommandOption::class);
95 | $option1->shouldReceive('toArray')
96 | ->once()
97 | ->andReturn($optionArray);
98 |
99 | $option2 = \Mockery::mock(CommandOption::class);
100 | $option2->shouldReceive('toArray')
101 | ->once()
102 | ->andReturn($option2Array);
103 |
104 | $command->option($option1);
105 | $command->option($option2);
106 |
107 | $result = $command->toArray();
108 | $this->assertEquals([
109 | 'type' => Command::TYPE_CHAT_INPUT,
110 | 'name' => $name,
111 | 'description' => $description,
112 | 'options' => [$optionArray, $option2Array],
113 | ], $result);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Commands/UserCommandTest.php:
--------------------------------------------------------------------------------
1 | expectedType = Command::TYPE_USER;
20 | $this->command = new UserCommand('test');
21 | }
22 |
23 | public function testToArray()
24 | {
25 | $name = 'test-name';
26 | $command = new UserCommand($name);
27 |
28 | $result = $command->toArray();
29 | $this->assertEquals([
30 | 'type' => Command::TYPE_USER,
31 | 'name' => $name,
32 | ], $result);
33 | }
34 |
35 | public function testToArrayWithOptions()
36 | {
37 | $name = 'test-name';
38 | $version = 'test-version';
39 | $defaultMemPermissions = 'test-perm';
40 | $parentAppId = 'test-app-id';
41 |
42 | $nameLocal = ['en-us'];
43 | $descLocal = ['es'];
44 |
45 | $command = new UserCommand($name);
46 |
47 | $command->version($version);
48 | $command->dmPermission();
49 | $command->defaultPermission();
50 | $command->defaultMemberPermissions($defaultMemPermissions);
51 | $command->nsfw();
52 | $command->parentApplication($parentAppId);
53 |
54 | $command->nameLocalizations($nameLocal);
55 | $command->descriptionLocalizations($descLocal);
56 |
57 | $result = $command->toArray();
58 | $this->assertEquals([
59 | 'type' => Command::TYPE_USER,
60 | 'name' => $name,
61 | 'application_id' => $parentAppId,
62 | 'default_member_permissions' => $defaultMemPermissions,
63 | 'dm_permission' => true,
64 | 'default_permission' => true,
65 | 'nsfw' => true,
66 | 'version' => $version,
67 | 'name_localizations' => $nameLocal,
68 | 'description_localizations' => $descLocal,
69 | ], $result);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Components/ActionRowTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
16 | 'type' => Component::TYPE_ACTION_ROW,
17 | ], $component->toArray());
18 | }
19 |
20 | public function testComponentWithComponents()
21 | {
22 | $expectedComponent1Array = ['k1' => 'v1'];
23 | $expectedComponent2Array = ['k2' => 'v2'];
24 |
25 | $component1 = \Mockery::mock(Component::class);
26 | $component2 = \Mockery::mock(Component::class);
27 |
28 | $component1->shouldReceive('toArray')->andReturn($expectedComponent1Array);
29 | $component2->shouldReceive('toArray')->andReturn($expectedComponent2Array);
30 |
31 | $component = new ActionRow([$component1]);
32 | $component->addComponent($component2);
33 |
34 | $this->assertEquals([
35 | 'type' => Component::TYPE_ACTION_ROW,
36 | 'components' => [$expectedComponent1Array, $expectedComponent2Array],
37 | ], $component->toArray());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Components/ButtonComponentTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
22 | 'type' => Component::TYPE_BUTTON,
23 | 'style' => GenericButtonComponent::STYLE_PRIMARY,
24 | 'label' => $label,
25 | 'custom_id' => $customId,
26 | ], $component->toArray());
27 | }
28 |
29 | public function testComponentWithOptions()
30 | {
31 | $label = 'test label';
32 | $customId = 'custom-id';
33 |
34 | $expectedEmojiArray = ['key' => 'value'];
35 |
36 | $emoji = \Mockery::mock(EmojiObject::class);
37 | $emoji->shouldReceive('toArray')->andReturn($expectedEmojiArray);
38 |
39 | $component = new ButtonComponent($label, $customId);
40 | $component->withEmoji($emoji);
41 | $component->disabled();
42 |
43 | $this->assertEquals([
44 | 'type' => Component::TYPE_BUTTON,
45 | 'style' => GenericButtonComponent::STYLE_PRIMARY,
46 | 'label' => $label,
47 | 'custom_id' => $customId,
48 | 'disabled' => true,
49 | 'emoji' => $expectedEmojiArray,
50 | ], $component->toArray());
51 | }
52 |
53 | /**
54 | * @dataProvider withStyleDataProvider
55 | */
56 | public function testComponentWithStyle(int $expectedStyle)
57 | {
58 | $label = 'test label';
59 | $customId = 'custom-id';
60 |
61 | $component = new ButtonComponent($label, $customId);
62 |
63 | switch ($expectedStyle) {
64 | case GenericButtonComponent::STYLE_PRIMARY:
65 | $component->withPrimaryStyle();
66 | break;
67 | case GenericButtonComponent::STYLE_SECONDARY:
68 | $component->withSecondaryStyle();
69 | break;
70 | case GenericButtonComponent::STYLE_SUCCESS:
71 | $component->withSuccessStyle();
72 | break;
73 | case GenericButtonComponent::STYLE_DANGER:
74 | $component->withDangerStyle();
75 | break;
76 | }
77 |
78 | $this->assertEquals([
79 | 'type' => Component::TYPE_BUTTON,
80 | 'style' => $expectedStyle,
81 | 'label' => $label,
82 | 'custom_id' => $customId,
83 | ], $component->toArray());
84 | }
85 |
86 | public function withStyleDataProvider(): array
87 | {
88 | return [
89 | [GenericButtonComponent::STYLE_PRIMARY],
90 | [GenericButtonComponent::STYLE_SECONDARY],
91 | [GenericButtonComponent::STYLE_SUCCESS],
92 | [GenericButtonComponent::STYLE_DANGER],
93 | ];
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Components/LinkButtonComponentTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
22 | 'type' => Component::TYPE_BUTTON,
23 | 'style' => GenericButtonComponent::STYLE_LINK,
24 | 'label' => $label,
25 | 'url' => $url,
26 | ], $component->toArray());
27 | }
28 |
29 | public function testComponentWithOptions()
30 | {
31 | $label = 'test label';
32 | $url = 'https://example.com';
33 |
34 | $expectedEmojiArray = ['key' => 'value'];
35 |
36 | $emoji = \Mockery::mock(EmojiObject::class);
37 | $emoji->shouldReceive('toArray')->andReturn($expectedEmojiArray);
38 |
39 | $component = new LinkButtonComponent($label, $url);
40 | $component->withEmoji($emoji);
41 | $component->disabled();
42 |
43 | $this->assertEquals([
44 | 'type' => Component::TYPE_BUTTON,
45 | 'style' => GenericButtonComponent::STYLE_LINK,
46 | 'label' => $label,
47 | 'disabled' => true,
48 | 'url' => $url,
49 | 'emoji' => $expectedEmojiArray,
50 | ], $component->toArray());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Components/ParagraphTextInputComponentTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
21 | 'type' => Component::TYPE_TEXT_INPUT,
22 | 'style' => GenericTextInputComponent::STYLE_PARAGRAPH,
23 | 'custom_id' => $customId,
24 | 'label' => $label,
25 | ], $component->toArray());
26 | }
27 |
28 | public function testComponentWithOptions()
29 | {
30 | $label = 'label';
31 | $customId = 'custom-id';
32 |
33 | $minLength = 5;
34 | $maxLength = 10;
35 | $placeholder = 'test placeholder';
36 | $value = 'test value';
37 |
38 | $component = new ParagraphTextInputComponent($label, $customId);
39 |
40 | $component->withPlaceholder($placeholder);
41 | $component->withMinLength($minLength);
42 | $component->withMaxLength($maxLength);
43 | $component->withValue($value);
44 | $component->required();
45 |
46 | $this->assertEquals([
47 | 'type' => Component::TYPE_TEXT_INPUT,
48 | 'style' => GenericTextInputComponent::STYLE_PARAGRAPH,
49 | 'custom_id' => $customId,
50 | 'label' => $label,
51 | 'min_length' => $minLength,
52 | 'max_length' => $maxLength,
53 | 'placeholder' => $placeholder,
54 | 'value' => $value,
55 | 'required' => true,
56 | ], $component->toArray());
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Components/SelectMenuComponentTest.php:
--------------------------------------------------------------------------------
1 | 'v1'];
18 | $expectedOption2Array = ['k2' => 'v2'];
19 |
20 | $option1 = \Mockery::mock(SelectOptionObject::class);
21 | $option2 = \Mockery::mock(SelectOptionObject::class);
22 |
23 | $option1->shouldReceive('toArray')->andReturn($expectedOption1Array);
24 | $option2->shouldReceive('toArray')->andReturn($expectedOption2Array);
25 |
26 | $component = new SelectMenuComponent($customId, [$option1, $option2]);
27 |
28 | $this->assertEquals([
29 | 'type' => Component::TYPE_SELECT_MENU,
30 | 'custom_id' => $customId,
31 | 'options' => [$expectedOption1Array, $expectedOption2Array],
32 | ], $component->toArray());
33 | }
34 |
35 | public function testComponentWithOptions()
36 | {
37 | $customId = 'custom-id';
38 |
39 | $expectedOption1Array = ['k1' => 'v1'];
40 | $expectedOption2Array = ['k2' => 'v2'];
41 |
42 | $option1 = \Mockery::mock(SelectOptionObject::class);
43 | $option2 = \Mockery::mock(SelectOptionObject::class);
44 |
45 | $option1->shouldReceive('toArray')->andReturn($expectedOption1Array);
46 | $option2->shouldReceive('toArray')->andReturn($expectedOption2Array);
47 |
48 | $component = new SelectMenuComponent($customId, [$option1, $option2]);
49 | $component->withPlaceholder('test placeholder');
50 | $component->withMinValues(5);
51 | $component->withMaxValues(10);
52 | $component->disabled();
53 |
54 | $this->assertEquals([
55 | 'type' => Component::TYPE_SELECT_MENU,
56 | 'custom_id' => $customId,
57 | 'options' => [$expectedOption1Array, $expectedOption2Array],
58 | 'placeholder' => 'test placeholder',
59 | 'min_values' => 5,
60 | 'max_values' => 10,
61 | 'disabled' => true,
62 | ], $component->toArray());
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Components/ShortTextInputComponentTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
21 | 'type' => Component::TYPE_TEXT_INPUT,
22 | 'style' => GenericTextInputComponent::STYLE_SHORT,
23 | 'custom_id' => $customId,
24 | 'label' => $label,
25 | ], $component->toArray());
26 | }
27 |
28 | public function testComponentWithOptions()
29 | {
30 | $label = 'label';
31 | $customId = 'custom-id';
32 |
33 | $minLength = 5;
34 | $maxLength = 10;
35 | $placeholder = 'test placeholder';
36 | $value = 'test value';
37 |
38 | $component = new ShortTextInputComponent($label, $customId);
39 |
40 | $component->withPlaceholder($placeholder);
41 | $component->withMinLength($minLength);
42 | $component->withMaxLength($maxLength);
43 | $component->withValue($value);
44 | $component->required();
45 |
46 | $this->assertEquals([
47 | 'type' => Component::TYPE_TEXT_INPUT,
48 | 'style' => GenericTextInputComponent::STYLE_SHORT,
49 | 'custom_id' => $customId,
50 | 'label' => $label,
51 | 'min_length' => $minLength,
52 | 'max_length' => $maxLength,
53 | 'placeholder' => $placeholder,
54 | 'value' => $value,
55 | 'required' => true,
56 | ], $component->toArray());
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Embeds/AuthorEmbedTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
18 | 'type' => Embed::TYPE_AUTHOR,
19 | 'author' => [
20 | 'name' => $name,
21 | ],
22 | ], $embed->toArray());
23 | }
24 |
25 | public function testEmbedWithOptions()
26 | {
27 | $name = 'test name';
28 | $title = 'test title';
29 | $description = 'test description';
30 | $timestamp = '12345';
31 |
32 | $url = 'https://example.com';
33 | $iconUrl = 'https://example.com/icon';
34 | $proxyIconUrl = 'https://example.com/proxy';
35 |
36 | $embed = new AuthorEmbed($name, $title, $description, $timestamp);
37 |
38 | $embed->withUrl($url);
39 | $embed->withIconUrl($iconUrl);
40 | $embed->withProxyIconUrl($proxyIconUrl);
41 |
42 | $this->assertEquals([
43 | 'type' => Embed::TYPE_AUTHOR,
44 | 'author' => [
45 | 'name' => $name,
46 | 'url' => $url,
47 | 'icon_url' => $iconUrl,
48 | 'proxy_icon_url' => $proxyIconUrl,
49 | ],
50 | 'title' => $title,
51 | 'description' => $description,
52 | 'timestamp' => $timestamp,
53 | ], $embed->toArray());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Embeds/FieldEmbedTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
19 | 'type' => Embed::TYPE_FIELD,
20 | 'field' => [
21 | 'name' => $name,
22 | 'value' => $value,
23 | ],
24 | ], $embed->toArray());
25 | }
26 |
27 | public function testEmbedWithOptions()
28 | {
29 | $name = 'test name';
30 | $value = 'test value';
31 | $color = 12345;
32 | $title = 'test title';
33 | $description = 'test description';
34 | $timestamp = '12345';
35 |
36 | $embed = new FieldEmbed($name, $value, $title, $description, $timestamp);
37 | $embed->inline();
38 | $embed->withColor($color);
39 |
40 | $this->assertEquals([
41 | 'type' => Embed::TYPE_FIELD,
42 | 'field' => [
43 | 'name' => $name,
44 | 'value' => $value,
45 | 'inline' => true,
46 | ],
47 | 'title' => $title,
48 | 'description' => $description,
49 | 'timestamp' => $timestamp,
50 | 'color' => $color,
51 | ], $embed->toArray());
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Embeds/FooterEmbedTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
18 | 'type' => Embed::TYPE_FOOTER,
19 | 'footer' => [
20 | 'text' => $text,
21 | ],
22 | ], $embed->toArray());
23 | }
24 |
25 | public function testEmbedWithOptions()
26 | {
27 | $text = 'test text';
28 | $title = 'test title';
29 | $description = 'test description';
30 | $timestamp = '12345';
31 |
32 | $iconUrl = 'https://example.com/proxy';
33 | $proxyIconUrl = 'https://example.com/proxy';
34 |
35 | $embed = new FooterEmbed($text, $title, $description, $timestamp);
36 |
37 | $embed->withIconUrl($iconUrl);
38 | $embed->withProxyIconUrl($proxyIconUrl);
39 | $embed->withColor(12345);
40 |
41 | $this->assertEquals([
42 | 'type' => Embed::TYPE_FOOTER,
43 | 'footer' => [
44 | 'text' => $text,
45 | 'icon_url' => $iconUrl,
46 | 'proxy_icon_url' => $proxyIconUrl,
47 | ],
48 | 'title' => $title,
49 | 'description' => $description,
50 | 'timestamp' => $timestamp,
51 | 'color' => 12345,
52 | ], $embed->toArray());
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Embeds/GenericEmbedTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
23 | 'type' => Embed::TYPE_RICH,
24 | 'title' => 'test title',
25 | 'description' => 'test description',
26 | ], $embed->toArray());
27 | }
28 |
29 | public function testEmbedWithOptions()
30 | {
31 | $title = 'test title';
32 | $description = 'test description';
33 |
34 | $embed = new GenericEmbed($title, $description);
35 |
36 | $embed->withColor(42);
37 |
38 | $this->assertEquals([
39 | 'type' => Embed::TYPE_RICH,
40 | 'title' => $title,
41 | 'description' => $description,
42 | 'color' => 42,
43 | ], $embed->toArray());
44 | }
45 |
46 | public function testEmbedWithAllOptions()
47 | {
48 | $title = 'test title';
49 | $description = 'test description';
50 |
51 | $footer = new FooterEmbed('test footer');
52 | $author = new AuthorEmbed('test author');
53 | $provider = new ProviderEmbed('test provider');
54 | $image = new ImageEmbed('test image');
55 | $thumbnail = new ThumbnailEmbed('test thumbnail');
56 | $video = new VideoEmbed('test video');
57 |
58 | $embed = new GenericEmbed($title, $description);
59 |
60 | $embed->withColor(42);
61 |
62 | $embed->withFooter($footer);
63 | $embed->withAuthor($author);
64 | $embed->withProvider($provider);
65 | $embed->withImage($image);
66 | $embed->withThumbnail($thumbnail);
67 | $embed->withVideo($video);
68 |
69 | $field1 = new FieldEmbed('test field 1', 'test field 1 value');
70 | $field2 = new FieldEmbed('test field 2', 'test field 2 value');
71 |
72 | $embed->addField($field1)
73 | ->addField($field2);
74 |
75 | $this->assertEquals([
76 | 'type' => Embed::TYPE_RICH,
77 | 'title' => $title,
78 | 'description' => $description,
79 | 'color' => 42,
80 | 'footer' => $footer->toArray(),
81 | 'author' => $author->toArray(),
82 | 'provider' => $provider->toArray(),
83 | 'image' => $image->toArray(),
84 | 'thumbnail' => $thumbnail->toArray(),
85 | 'video' => $video->toArray(),
86 | 'fields' => [
87 | $field1->toArray()['field'],
88 | $field2->toArray()['field'],
89 | ],
90 | ], $embed->toArray());
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Embeds/ImageEmbedTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
18 | 'type' => Embed::TYPE_IMAGE,
19 | 'image' => [
20 | 'url' => $url,
21 | ],
22 | ], $embed->toArray());
23 | }
24 |
25 | public function testEmbedWithOptions()
26 | {
27 | $url = 'https://example.com';
28 | $title = 'test title';
29 | $description = 'test description';
30 | $timestamp = '12345';
31 |
32 | $proxyUrl = 'https://example.com/proxy';
33 | $height = 256;
34 | $width = 512;
35 |
36 | $embed = new ImageEmbed($url, $title, $description, $timestamp);
37 |
38 | $embed->withProxyUrl($proxyUrl);
39 | $embed->withWidth($width);
40 | $embed->withHeight($height);
41 |
42 | $this->assertEquals([
43 | 'type' => Embed::TYPE_IMAGE,
44 | 'image' => [
45 | 'url' => $url,
46 | 'proxy_url' => $proxyUrl,
47 | 'height' => $height,
48 | 'width' => $width,
49 | ],
50 | 'title' => $title,
51 | 'description' => $description,
52 | 'timestamp' => $timestamp,
53 | ], $embed->toArray());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Embeds/ProviderEmbedTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
16 | 'type' => Embed::TYPE_PROVIDER,
17 | ], $embed->toArray());
18 | }
19 |
20 | public function testEmbedWithOptions()
21 | {
22 | $title = 'test title';
23 | $description = 'test description';
24 | $timestamp = '12345';
25 | $url = 'https://example.com';
26 | $name = 'test name';
27 |
28 | $embed = new ProviderEmbed($title, $description, $timestamp);
29 |
30 | $embed->withName($name);
31 | $embed->withUrl($url);
32 |
33 | $this->assertEquals([
34 | 'type' => Embed::TYPE_PROVIDER,
35 | 'provider' => [
36 | 'url' => $url,
37 | 'name' => $name,
38 | ],
39 | 'title' => $title,
40 | 'description' => $description,
41 | 'timestamp' => $timestamp,
42 | ], $embed->toArray());
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Embeds/ThumbnailEmbedTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
18 | 'type' => Embed::TYPE_THUMBNAIL,
19 | 'image' => [
20 | 'url' => $url,
21 | ],
22 | ], $embed->toArray());
23 | }
24 |
25 | public function testEmbedWithOptions()
26 | {
27 | $url = 'https://example.com';
28 | $title = 'test title';
29 | $description = 'test description';
30 | $timestamp = '12345';
31 |
32 | $proxyUrl = 'https://example.com/proxy';
33 | $height = 256;
34 | $width = 512;
35 |
36 | $embed = new ThumbnailEmbed($url, $title, $description, $timestamp);
37 |
38 | $embed->withProxyUrl($proxyUrl);
39 | $embed->withWidth($width);
40 | $embed->withHeight($height);
41 |
42 | $this->assertEquals([
43 | 'type' => Embed::TYPE_THUMBNAIL,
44 | 'image' => [
45 | 'url' => $url,
46 | 'proxy_url' => $proxyUrl,
47 | 'height' => $height,
48 | 'width' => $width,
49 | ],
50 | 'title' => $title,
51 | 'description' => $description,
52 | 'timestamp' => $timestamp,
53 | ], $embed->toArray());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Embeds/VideoEmbedTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
18 | 'type' => Embed::TYPE_VIDEO,
19 | 'video' => [
20 | 'url' => $url,
21 | ],
22 | ], $embed->toArray());
23 | }
24 |
25 | public function testEmbedWithOptions()
26 | {
27 | $url = 'https://example.com';
28 | $title = 'test title';
29 | $description = 'test description';
30 | $timestamp = '12345';
31 |
32 | $proxyUrl = 'https://example.com/proxy';
33 | $height = 256;
34 | $width = 512;
35 |
36 | $embed = new VideoEmbed($url, $title, $description, $timestamp);
37 |
38 | $embed->withProxyUrl($proxyUrl);
39 | $embed->withWidth($width);
40 | $embed->withHeight($height);
41 |
42 | $this->assertEquals([
43 | 'type' => Embed::TYPE_VIDEO,
44 | 'video' => [
45 | 'url' => $url,
46 | 'proxy_url' => $proxyUrl,
47 | 'height' => $height,
48 | 'width' => $width,
49 | ],
50 | 'title' => $title,
51 | 'description' => $description,
52 | 'timestamp' => $timestamp,
53 | ], $embed->toArray());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Interactions/DiscordInteractionResponseTest.php:
--------------------------------------------------------------------------------
1 | 'value'];
15 | $type = 12;
16 |
17 | $response = new DiscordInteractionResponse($type, $data, $code);
18 |
19 | $this->assertSame($code, $response->getStatus());
20 | $this->assertEquals([
21 | 'type' => $type,
22 | 'data' => $data,
23 | ], $response->toArray());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Interactions/Handlers/PingHandlerTest.php:
--------------------------------------------------------------------------------
1 | handle(\Mockery::mock(Request::class));
17 |
18 | $this->assertSame(200, $result->getStatus());
19 | $this->assertEquals([
20 | 'type' => InteractionHandler::RESPONSE_TYPE_PONG,
21 | ], $result->toArray());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Objects/AllowedMentionObjectTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([], $object->toArray());
15 | }
16 |
17 | public function testObjectWithParseOption()
18 | {
19 | $object = new AllowedMentionObject();
20 |
21 | $object->allowRolesMention();
22 | $this->assertEquals([
23 | 'parse' => [AllowedMentionObject::MENTIONS_ROLES],
24 | ], $object->toArray());
25 |
26 | $object->allowUsersMention();
27 | $this->assertEquals([
28 | 'parse' => [AllowedMentionObject::MENTIONS_ROLES, AllowedMentionObject::MENTIONS_USERS],
29 | ], $object->toArray());
30 |
31 | $object->allowEveryoneMention();
32 | $this->assertEquals([
33 | 'parse' => [AllowedMentionObject::MENTIONS_ROLES, AllowedMentionObject::MENTIONS_USERS, AllowedMentionObject::MENTIONS_EVERYONE],
34 | ], $object->toArray());
35 | }
36 |
37 | public function testObjectWithAdditionalOptions()
38 | {
39 | $object = new AllowedMentionObject();
40 |
41 | $object->allowMentionsForRoles(['role-1', 'role-2']);
42 | $object->allowMentionsForUsers(['user-1', 'user-2']);
43 | $object->mentionReplyUser();
44 |
45 | $this->assertEquals([
46 | 'replied_user' => true,
47 | 'roles' => ['role-1', 'role-2'],
48 | 'users' => ['user-1', 'user-2'],
49 | ], $object->toArray());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Objects/EmojiObjectTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
17 | 'name' => $name,
18 | ], $object->toArray());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Unit/Support/Objects/SelectOptionObjectTest.php:
--------------------------------------------------------------------------------
1 | assertEquals([
20 | 'label' => $label,
21 | 'value' => $value,
22 | ], $object->toArray());
23 | }
24 |
25 | public function testObjectWithOptions()
26 | {
27 | $label = 'test label';
28 | $value = 'test value';
29 |
30 | $expectedEmojiArray = ['key' => 'value'];
31 |
32 | $emoji = \Mockery::mock(EmojiObject::class);
33 | $emoji->shouldReceive('toArray')->andReturn($expectedEmojiArray);
34 |
35 | $object = new SelectOptionObject($label, $value);
36 |
37 | $object->withEmoji($emoji);
38 | $object->withDescription('test description');
39 | $object->default();
40 |
41 | $this->assertEquals([
42 | 'label' => $label,
43 | 'value' => $value,
44 | 'emoji' => $expectedEmojiArray,
45 | 'default' => true,
46 | 'description' => 'test description',
47 | ], $object->toArray());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------