Skip to content

Commit

Permalink
Merge pull request #649 from andrey-helldar/patch/2024-09-20/12-13
Browse files Browse the repository at this point in the history
Adding reaction processing when retrieving in webhooks
  • Loading branch information
fabio-ivona authored Oct 2, 2024
2 parents d5d6f7e + 91b3163 commit 353f210
Show file tree
Hide file tree
Showing 9 changed files with 563 additions and 4 deletions.
10 changes: 10 additions & 0 deletions config/telegraph.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@
*/
'max_connections' => env('TELEGRAPH_WEBHOOK_MAX_CONNECTIONS', 40),

/**
* List of event types for which the webhook should fire.
*
* Specify a null to receive all update types except `chat_member`, `message_reaction`,
* and `message_reaction_count` (by default).
*
* @see https://core.telegram.org/bots/api#setwebhook
*/
'allowed_updates' => null,

/*
* If enabled, Telegraph dumps received
* webhook messages to logs
Expand Down
23 changes: 22 additions & 1 deletion docs/15.webhooks/4.webhook-request-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ navigation.title: 'Request Types'
---


Telegraph can handle four incoming webhook request types: **Chat Messages**, **Chat Commands**, **Callback Queries** and **Inline Queries**:
Telegraph can handle four incoming webhook request types: **Chat Messages**, **Message Reactions**, **Chat Commands**, **Callback Queries** and **Inline Queries**:

## Chat Messages

Expand Down Expand Up @@ -46,6 +46,27 @@ class CustomWebhookHandler extends WebhookHandler
}
```

## Message Reactions

Chat messages containing adding and/or deleting user reactions (emojis) to messages.

It can be handled by overriding `DefStudio\Telegraph\Handlers\WebhookHandler::handleChatReaction()` method:

```php
class CustomWebhookHandler extends WebhookHandler
{
protected function handleChatReaction(array $newReactions, array $oldReactions): void
{
// in this example, a received emoji message is sent back to the chat
$this->chat->html("Received: " . $newReactions[0]['emoji'])->send();
}
}
```

> [!WARNING]
> By default, Telegram does not report events related to reactions to messages.
> To interact with reactions, [specify](config/telegraph.php) in the settings all
> [types of messages](https://core.telegram.org/bots/api#update) for which you want to catch events.
## Chat Commands

Expand Down
12 changes: 11 additions & 1 deletion src/Concerns/InteractsWithWebhooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,16 @@ private function getWebhookUrl(): string
return $customWebhookUrl.route('telegraph.webhook', $this->getBot(), false);
}

public function registerWebhook(bool $dropPendingUpdates = null, int $maxConnections = null, string $secretToken = null): Telegraph
/**
* @param bool|null $dropPendingUpdates
* @param int|null $maxConnections
* @param string|null $secretToken
* @param string[]|null $allowedUpdates
*
* @throws \DefStudio\Telegraph\Exceptions\TelegramWebhookException
* @return \DefStudio\Telegraph\Telegraph
*/
public function registerWebhook(bool $dropPendingUpdates = null, int $maxConnections = null, string $secretToken = null, array $allowedUpdates = null): Telegraph
{
$telegraph = clone $this;

Expand All @@ -39,6 +48,7 @@ public function registerWebhook(bool $dropPendingUpdates = null, int $maxConnect
'drop_pending_updates' => $dropPendingUpdates,
'max_connections' => $maxConnections ?? config('telegraph.webhook.max_connections'),
'secret_token' => $secretToken ?? config('telegraph.webhook.secret_token'),
'allowed_updates' => $allowedUpdates ?? config('telegraph.webhook.allowed_updates'),
])->filter()
->toArray();

Expand Down
130 changes: 130 additions & 0 deletions src/DTO/Reaction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

declare(strict_types=1);

namespace DefStudio\Telegraph\DTO;

use Carbon\CarbonInterface;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Carbon;

/**
* @implements Arrayable<string, string|int|bool|array<string, mixed>>
*/
class Reaction implements Arrayable
{
private int $id;

private Chat $chat;
private ?Chat $actorChat = null;

private ?User $from = null;

/**
* @var array<array<string, string>>
*/
private array $oldReaction = [];

/**
* @var array<array<string, string>>
*/
private array $newReaction = [];

private CarbonInterface $date;

private function __construct()
{
}

/**
* @param array{
* message_id: int,
* chat: array<string, mixed>,
* actor_chat?: array<string, mixed>,
* user?: array<string, mixed>,
* date: int,
* old_reaction: array<int, array<string, string>>,
* new_reaction: array<int, array<string, string>>
* } $data
*/
public static function fromArray(array $data): Reaction
{
$reaction = new self();

$reaction->id = $data['message_id'];

/* @phpstan-ignore-next-line */
$reaction->chat = Chat::fromArray($data['chat']);

if (isset($data['actor_chat'])) {
/* @phpstan-ignore-next-line */
$reaction->actorChat = Chat::fromArray($data['actor_chat']);
}

if (isset($data['user'])) {
/* @phpstan-ignore-next-line */
$reaction->from = User::fromArray($data['user']);
}

$reaction->date = Carbon::createFromTimestamp($data['date']);

$reaction->oldReaction = $data['old_reaction'];
$reaction->newReaction = $data['new_reaction'];

return $reaction;
}

public function id(): int
{
return $this->id;
}

public function chat(): Chat
{
return $this->chat;
}

public function actorChat(): ?Chat
{
return $this->actorChat;
}

public function from(): ?User
{
return $this->from;
}

/**
* @return array<array<string, string>>
*/
public function oldReaction(): array
{
return $this->oldReaction;
}

/**
* @return array<array<string, string>>
*/
public function newReaction(): array
{
return $this->newReaction;
}

public function date(): CarbonInterface
{
return $this->date;
}

public function toArray(): array
{
return array_filter([
'id' => $this->id,
'chat' => $this->chat->toArray(),
'actor_chat' => $this->actorChat?->toArray(),
'from' => $this->from?->toArray(),
'old_reaction' => $this->oldReaction,
'new_reaction' => $this->newReaction,
'date' => $this->date->toISOString(),
], fn ($value) => $value !== null);
}
}
51 changes: 49 additions & 2 deletions src/Handlers/WebhookHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use DefStudio\Telegraph\DTO\Chat;
use DefStudio\Telegraph\DTO\InlineQuery;
use DefStudio\Telegraph\DTO\Message;
use DefStudio\Telegraph\DTO\Reaction;
use DefStudio\Telegraph\DTO\User;
use DefStudio\Telegraph\Exceptions\TelegramWebhookException;
use DefStudio\Telegraph\Keyboard\Keyboard;
Expand All @@ -37,10 +38,11 @@ abstract class WebhookHandler

protected Request $request;
protected Message|null $message = null;
protected Reaction|null $reaction = null;
protected CallbackQuery|null $callbackQuery = null;

/**
* @var Collection<string, string>
* @var Collection(<string, string>|<int, <array<string, string>>>)
*/
protected Collection $data;

Check failure on line 47 in src/Handlers/WebhookHandler.php

View workflow job for this annotation

GitHub Actions / phpstan

Property DefStudio\Telegraph\Handlers\WebhookHandler::$data with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue

Expand Down Expand Up @@ -131,6 +133,18 @@ protected function handleMessage(): void
$this->handleChatMessage($text);
}

protected function handleReaction(): void
{
$this->extractReactionData();

if (config('telegraph.debug_mode', config('telegraph.webhook.debug'))) {
Log::debug('Telegraph webhook message', $this->data->toArray());
}

/** @phpstan-ignore-next-line */
$this->handleChatReaction($this->reaction->newReaction(), $this->reaction->oldReaction());
}

protected function canHandle(string $action): bool
{
if ($action === 'handle') {
Expand Down Expand Up @@ -178,6 +192,17 @@ protected function extractMessageData(): void
]);
}

protected function extractReactionData(): void
{
$this->setupChat();

assert($this->reaction !== null);

$this->messageId = $this->reaction->id();

$this->data = collect($this->reaction->newReaction());
}

protected function handleChatMemberJoined(User $member): void
{
// .. do nothing
Expand All @@ -193,6 +218,17 @@ protected function handleChatMessage(Stringable $text): void
// .. do nothing
}

/**
* @param array<array<string, string>> $newReactions
* @param array<array<string, string>> $oldReactions
*
* @return void
*/
protected function handleChatReaction(array $newReactions, array $oldReactions): void
{
// .. do nothing
}

protected function replaceKeyboard(Keyboard $newKeyboard): void
{
$this->chat->replaceKeyboard($this->messageId, $newKeyboard)->send();
Expand Down Expand Up @@ -250,6 +286,14 @@ public function handle(Request $request, TelegraphBot $bot): void
return;
}

if ($this->request->has('message_reaction')) {
/* @phpstan-ignore-next-line */
$this->reaction = Reaction::fromArray($this->request->input('message_reaction'));
$this->handleReaction();

return;
}


if ($this->request->has('callback_query')) {
/* @phpstan-ignore-next-line */
Expand All @@ -275,6 +319,8 @@ protected function setupChat(): void
{
if (isset($this->message)) {
$telegramChat = $this->message->chat();
} elseif (isset($this->reaction)) {
$telegramChat = $this->reaction->chat();
} else {
$telegramChat = $this->callbackQuery?->message()?->chat();
}
Expand All @@ -301,7 +347,8 @@ protected function setupChat(): void
protected function allowUnknownChat(): bool
{
return (bool) match (true) {
$this->message !== null => config('telegraph.security.allow_messages_from_unknown_chats', false),
$this->message !== null,
$this->reaction !== null => config('telegraph.security.allow_messages_from_unknown_chats', false),
$this->callbackQuery != null => config('telegraph.security.allow_callback_queries_from_unknown_chats', false),
default => false,
};
Expand Down
28 changes: 28 additions & 0 deletions tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,34 @@ function webhook_message($handler = TestWebhookHandler::class, array $message =
]);
}

function webhook_message_reaction($handler = TestWebhookHandler::class, array $message = null): Request
{
register_webhook_handler($handler);

return Request::create('', 'POST', [
'message_reaction' => $message ?? [
'chat' => [
'id' => 3,
'type' => 'a',
'title' => 'b',
],
'date' => 1727211008,
'user' => [
'id' => 1,
'first_name' => 'a',
],
'message_id' => 2,
'new_reaction' => [
[
'type' => 'emoji',
'emoji' => '👍',
],
],
'old_reaction' => [],
],
]);
}

function webhook_request($action = 'invalid', $handler = TestWebhookHandler::class, int $chat_id = -123456789): Request
{
register_webhook_handler($handler);
Expand Down
8 changes: 8 additions & 0 deletions tests/Support/TestWebhookHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,12 @@ protected function handleChatMemberLeft(User $member): void
{
$this->chat->html("{$member->firstName()} just left")->send();
}

protected function handleChatReaction(array $newReactions, array $oldReactions): void
{
$this->chat->html(implode(':', [
'New reaction is ' . $newReactions[0]['emoji'],
'Old reaction is ' . $oldReactions[0]['emoji'],
]))->send();
}
}
Loading

0 comments on commit 353f210

Please sign in to comment.