diff --git a/docs/12.features/9.dto.md b/docs/12.features/9.dto.md index bb67bb6a..0b739bba 100644 --- a/docs/12.features/9.dto.md +++ b/docs/12.features/9.dto.md @@ -10,6 +10,7 @@ contains incoming data (a message or a callback query) - `->id()` incoming _update_id_ - `->message()` (optional) an instance of [`Message`](#message) +- `->messageReaction()` (optional) an instance of [`Reaction`](#reaction) - `->callbackQuery()` (optional) an instance of [`CallbackQuery`](#callback-query) ## `Chat` @@ -57,6 +58,22 @@ contains incoming data (a message or a callback query) - `->message()` (optional) an instance of the [`Message`](#message) that triggered the callback query - `->data()` an `Illuminate\Support\Collection` that holds the key/value pairs of the callback query data +## `Reaction` + +- `->id()` incoming _message_id_ +- `->chat()` an instance of [`Chat`](#chat) holding data about the chat to which the message belongs to +- `->actorChat()` (optional) an instance of [`Chat`](#chat) holding data about the chat to which the chat on behalf of which the reaction was changed, if the user is anonymous +- `->from()` (optional) an instance of [`User`](#user) holding data about the message's sender +- `->oldReaction()` a collection of [`ReactionType`](#reactiontype) holding data about the contained reaction type resolutions +- `->newReaction()` a collection of [`ReactionType`](#reactiontype) holding data about the contained reaction type resolutions +- `->date()` a `CarbonInterface` holding the message sent + +## `ReactionType` + +- `->type()` type of the reaction +- `->emoji()` reaction emoji +- `->customEmojiId()` (optional) custom emoji identifier + ## `User` diff --git a/src/DTO/Reaction.php b/src/DTO/Reaction.php index 5f6cc22d..12740f05 100644 --- a/src/DTO/Reaction.php +++ b/src/DTO/Reaction.php @@ -7,6 +7,7 @@ use Carbon\CarbonInterface; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Carbon; +use Illuminate\Support\Collection; /** * @implements Arrayable> @@ -21,19 +22,21 @@ class Reaction implements Arrayable private ?User $from = null; /** - * @var array> + * @var Collection */ - private array $oldReaction = []; + private Collection $oldReaction; /** - * @var array> + * @var Collection */ - private array $newReaction = []; + private Collection $newReaction; private CarbonInterface $date; private function __construct() { + $this->oldReaction = Collection::empty(); + $this->newReaction = Collection::empty(); } /** @@ -68,8 +71,11 @@ public static function fromArray(array $data): Reaction $reaction->date = Carbon::createFromTimestamp($data['date']); - $reaction->oldReaction = $data['old_reaction']; - $reaction->newReaction = $data['new_reaction']; + /* @phpstan-ignore-next-line */ + $reaction->oldReaction = collect($data['old_reaction'] ?? [])->map(fn (array $reactionData) => ReactionType::fromArray($reactionData)); + + /* @phpstan-ignore-next-line */ + $reaction->newReaction = collect($data['new_reaction'] ?? [])->map(fn (array $reactionData) => ReactionType::fromArray($reactionData)); return $reaction; } @@ -95,17 +101,17 @@ public function from(): ?User } /** - * @return array> + * @return Collection */ - public function oldReaction(): array + public function oldReaction(): Collection { return $this->oldReaction; } /** - * @return array> + * @return Collection */ - public function newReaction(): array + public function newReaction(): Collection { return $this->newReaction; } @@ -122,8 +128,8 @@ public function toArray(): array 'chat' => $this->chat->toArray(), 'actor_chat' => $this->actorChat?->toArray(), 'from' => $this->from?->toArray(), - 'old_reaction' => $this->oldReaction, - 'new_reaction' => $this->newReaction, + 'old_reaction' => $this->oldReaction->toArray(), + 'new_reaction' => $this->newReaction->toArray(), 'date' => $this->date->toISOString(), ], fn ($value) => $value !== null); } diff --git a/src/DTO/ReactionType.php b/src/DTO/ReactionType.php new file mode 100644 index 00000000..39203fae --- /dev/null +++ b/src/DTO/ReactionType.php @@ -0,0 +1,67 @@ + + */ +class ReactionType implements Arrayable +{ + public const TYPE_EMOJI = 'emoji'; + public const TYPE_CUSTOM_EMOJI = 'custom_emoji'; + public const TYPE_PAID_EMOJI = 'paid'; + + private string $type; + private string $emoji; + private ?string $customEmojiId = null; + + private function __construct() + { + } + + /** + * @param array{ + * type: string, + * emoji: string, + * custom_emoji_id?: string + * } $data + */ + public static function fromArray(array $data): ReactionType + { + $reaction = new self(); + + $reaction->type = $data['type']; + $reaction->emoji = $data['emoji']; + $reaction->customEmojiId = $data['custom_emoji_id'] ?? null; + + return $reaction; + } + + public function type(): string + { + return $this->type; + } + + public function emoji(): string + { + return $this->emoji; + } + + public function customEmojiId(): ?string + { + return $this->customEmojiId; + } + + public function toArray(): array + { + return array_filter([ + 'type' => $this->type, + 'emoji' => $this->emoji, + 'custom_emoji_id' => $this->customEmojiId, + ], fn ($value) => $value !== null); + } +} diff --git a/src/DTO/TelegramUpdate.php b/src/DTO/TelegramUpdate.php index c3cdd6b6..4c98e077 100644 --- a/src/DTO/TelegramUpdate.php +++ b/src/DTO/TelegramUpdate.php @@ -15,6 +15,7 @@ class TelegramUpdate implements Arrayable { private int $id; private ?Message $message = null; + private ?Reaction $messageReaction = null; private ?CallbackQuery $callbackQuery = null; private ?ChatMemberUpdate $botChatStatusChange = null; private ?InlineQuery $inlineQuery = null; @@ -28,6 +29,7 @@ private function __construct() * update_id:int, * message?:array, * edited_message?:array, + * message_reaction?:array, * channel_post?:array, * callback_query?:array, * my_chat_member?:array, @@ -50,6 +52,11 @@ public static function fromArray(array $data): TelegramUpdate $update->message = Message::fromArray($data['edited_message']); } + if (isset($data['message_reaction'])) { + /* @phpstan-ignore-next-line */ + $update->messageReaction = Reaction::fromArray($data['message_reaction']); + } + if (isset($data['channel_post'])) { /* @phpstan-ignore-next-line */ $update->message = Message::fromArray($data['channel_post']); @@ -83,6 +90,11 @@ public function message(): ?Message return $this->message; } + public function messageReaction(): ?Reaction + { + return $this->messageReaction; + } + public function callbackQuery(): ?CallbackQuery { return $this->callbackQuery; @@ -103,6 +115,7 @@ public function toArray(): array return array_filter([ 'id' => $this->id, 'message' => $this->message?->toArray(), + 'message_reaction' => $this->messageReaction?->toArray(), 'callback_query' => $this->callbackQuery?->toArray(), 'bot_chat_status_change' => $this->botChatStatusChange?->toArray(), 'inline_query' => $this->inlineQuery?->toArray(), diff --git a/src/Handlers/WebhookHandler.php b/src/Handlers/WebhookHandler.php index 5fdb4510..cf1011e3 100644 --- a/src/Handlers/WebhookHandler.php +++ b/src/Handlers/WebhookHandler.php @@ -219,12 +219,12 @@ protected function handleChatMessage(Stringable $text): void } /** - * @param array> $newReactions - * @param array> $oldReactions + * @param Collection $newReactions + * @param Collection $oldReactions * * @return void */ - protected function handleChatReaction(array $newReactions, array $oldReactions): void + protected function handleChatReaction(Collection $newReactions, Collection $oldReactions): void { // .. do nothing } diff --git a/tests/Support/TestWebhookHandler.php b/tests/Support/TestWebhookHandler.php index 8b23e75b..a9e9559b 100644 --- a/tests/Support/TestWebhookHandler.php +++ b/tests/Support/TestWebhookHandler.php @@ -13,6 +13,7 @@ use DefStudio\Telegraph\Keyboard\Button; use DefStudio\Telegraph\Keyboard\Keyboard; use Exception; +use Illuminate\Support\Collection; use Illuminate\Support\Stringable; class TestWebhookHandler extends WebhookHandler @@ -141,11 +142,13 @@ protected function handleChatMemberLeft(User $member): void $this->chat->html("{$member->firstName()} just left")->send(); } - protected function handleChatReaction(array $newReactions, array $oldReactions): void + protected function handleChatReaction(Collection $newReactions, Collection $oldReactions): void { $this->chat->html(implode(':', [ - 'New reaction is ' . $newReactions[0]['emoji'], - 'Old reaction is ' . $oldReactions[0]['emoji'], + /* @phpstan-ignore-next-line */ + 'New reaction is ' . $newReactions->first()->emoji(), + /* @phpstan-ignore-next-line */ + 'Old reaction is ' . $oldReactions->first()->emoji(), ]))->send(); } } diff --git a/tests/Unit/DTO/ReactionTest.php b/tests/Unit/DTO/ReactionTest.php index b9e0bbe5..bfaa0427 100644 --- a/tests/Unit/DTO/ReactionTest.php +++ b/tests/Unit/DTO/ReactionTest.php @@ -206,7 +206,7 @@ ], ]); - expect($dto->oldReaction())->toBe([ + expect($dto->oldReaction()->toArray())->toBe([ [ 'type' => 'emoji', 'emoji' => '🔥', @@ -251,7 +251,7 @@ ], ]); - expect($dto->newReaction())->toBe([ + expect($dto->newReaction()->toArray())->toBe([ [ 'type' => 'emoji', 'emoji' => '👍', diff --git a/tests/Unit/DTO/TelegramUpdateTest.php b/tests/Unit/DTO/TelegramUpdateTest.php index 3c83f4ba..35661595 100644 --- a/tests/Unit/DTO/TelegramUpdateTest.php +++ b/tests/Unit/DTO/TelegramUpdateTest.php @@ -18,6 +18,39 @@ 'date' => now()->timestamp, 'text' => 'f', ], + 'message_reaction' => [ + 'message_id' => 2, + 'date' => now()->timestamp, + 'chat' => [ + 'id' => 3, + 'type' => 'a', + 'title' => 'b', + ], + 'actor_chat' => [ + 'id' => 3, + 'type' => 'a', + 'title' => 'b', + ], + 'user' => [ + 'id' => 1, + 'is_bot' => true, + 'first_name' => 'a', + 'last_name' => 'b', + 'username' => 'c', + ], + 'new_reaction' => [ + [ + 'type' => 'emoji', + 'emoji' => '👍', + ], + ], + 'old_reaction' => [ + [ + 'type' => 'emoji', + 'emoji' => '🔥', + ], + ], + ], 'channel_post' => [ 'message_id' => 4, 'date' => now()->timestamp, diff --git a/tests/Unit/Handlers/WebhookHandlerTest.php b/tests/Unit/Handlers/WebhookHandlerTest.php index e8479406..6e7bad2b 100644 --- a/tests/Unit/Handlers/WebhookHandlerTest.php +++ b/tests/Unit/Handlers/WebhookHandlerTest.php @@ -451,7 +451,10 @@ ], ]), $bot); - Facade::assertSent("New reaction is 👍:Old reaction is 🔥"); + Facade::assertSent(implode(':', [ + 'New reaction is 👍', + 'Old reaction is 🔥', + ])); }); it('does not crash on errors', function () {