Skip to content

Commit

Permalink
Merge pull request #650 from andrey-helldar/patch/2024-09-29/23-38
Browse files Browse the repository at this point in the history
Added entities processing
  • Loading branch information
fabio-ivona authored Oct 3, 2024
2 parents 5e3cd22 + ce339a0 commit 55a3fe7
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 2 deletions.
11 changes: 11 additions & 0 deletions docs/12.features/9.dto.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ contains incoming data (a message or a callback query)
- `->contact()` (optional) an instance of [`Contact`](#contact) holding data about the contained contact data
- `->voice()` (optional) an instance of [`Voice`](#voice) holding data about the contained voical message
- `->sticker()` (optional) an instance of [`Sticker`](#sticker) holding data about the contained sticker
- `->entities()` (optional) a collection of [`Entity`](#entity) holding data about the contained entity
- `->newChatMembers()` a collection of [`User`](#user) holding the list of users that joined the group/supergroup
- `->leftChatMember()` (optional) an instance of [`User`](#user) holding data about the user that left the group/supergroup
- `->webAppData()` (optional) incoming data from sendData method of telegram WebApp
Expand Down Expand Up @@ -148,6 +149,16 @@ contains incoming data (a message or a callback query)
- `->filesize()` (optional) sticker file size in Bytes
- `->thumbnail()` (optional) an instance of the [`Photo`](#photo) that holds data about the thumbnail

## `Entity`

- `->type()` type of the entity
- `->offset()` offset in UTF-16 code units to the start of the entity
- `->length()` length of the entity in utf-16 code units
- `->url()` (optional) for “text_link” only, URL that will be opened after user taps on the text
- `->user()` (optional) for “text_mention” only, the mentioned [`User`](#user)
- `->language()` (optional) for “pre” only, the programming language of the entity text
- `->customEmojiId()` (optional) for “custom_emoji” only, unique identifier of the custom emoji

## `WriteAccessAllowed`

- `->fromRequest()` true, if the access was granted after the user accepted an explicit request from a Web App sent by the method [requestWriteAccess](https://core.telegram.org/bots/webapps#initializing-mini-apps)
Expand Down
120 changes: 120 additions & 0 deletions src/DTO/Entity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

declare(strict_types=1);

namespace DefStudio\Telegraph\DTO;

use Illuminate\Contracts\Support\Arrayable;

/**
* @implements Arrayable<string, string|int|array<string, mixed>>
*/
class Entity implements Arrayable
{
private string $type;

private int $offset;

private int $length;

private ?string $url = null;

private ?User $user = null;

private ?string $language = null;

private ?string $customEmojiId = null;

private function __construct()
{
}

/**
* @param array{
* type: string,
* offset: int,
* length: int,
* url?: string,
* user?: array<string, mixed>,
* language?: string,
* custom_emoji_id?: string
* } $data
*
* @return \DefStudio\Telegraph\DTO\Entity
*/
public static function fromArray(array $data): Entity
{
$entity = new self();

$entity->type = $data['type'];
$entity->offset = $data['offset'];
$entity->length = $data['length'];

if (isset($data['url'])) {
$entity->url = $data['url'];
}

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

if (isset($data['language'])) {
$entity->language = $data['language'];
}

if (isset($data['custom_emoji_id'])) {
$entity->customEmojiId = $data['custom_emoji_id'];
}

return $entity;
}

public function type(): string
{
return $this->type;
}

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

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

public function url(): ?string
{
return $this->url;
}

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

public function language(): ?string
{
return $this->language;
}

public function customEmojiId(): ?string
{
return $this->customEmojiId;
}

public function toArray(): array
{
return array_filter([
'type' => $this->type,
'offset' => $this->offset,
'length' => $this->length,
'url' => $this->url,
'user' => $this->user()?->toArray(),
'language' => $this->language,
'custom_emoji_id' => $this->customEmojiId,
], fn ($value) => $value !== null);
}
}
16 changes: 16 additions & 0 deletions src/DTO/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@ class Message implements Arrayable

private ?WriteAccessAllowed $writeAccessAllowed = null;

/** @var Collection<array-key, Entity> */
private Collection $entities;

private function __construct()
{
$this->photos = Collection::empty();
$this->entities = Collection::empty();
}

/**
Expand Down Expand Up @@ -85,6 +89,7 @@ private function __construct()
* left_chat_member?: array<string, mixed>,
* web_app_data?: array<string, mixed>,
* write_access_allowed?: array<string, mixed>,
* entities?: array<object>
* } $data
*/
public static function fromArray(array $data): Message
Expand Down Expand Up @@ -202,6 +207,11 @@ public static function fromArray(array $data): Message
$message->writeAccessAllowed = WriteAccessAllowed::fromArray($data['write_access_allowed']);
}

if (isset($data['entities']) && $data['entities']) {
/* @phpstan-ignore-next-line */
$message->entities = collect($data['entities'])->map(fn (array $entity) => Entity::fromArray($entity));
}

return $message;
}

Expand Down Expand Up @@ -331,6 +341,11 @@ public function writeAccessAllowed(): ?WriteAccessAllowed
return $this->writeAccessAllowed;
}

public function entities(): Collection

Check failure on line 344 in src/DTO/Message.php

View workflow job for this annotation

GitHub Actions / phpstan

Method DefStudio\Telegraph\DTO\Message::entities() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue

Check failure on line 344 in src/DTO/Message.php

View workflow job for this annotation

GitHub Actions / phpstan

Method DefStudio\Telegraph\DTO\Message::entities() return type with generic class Illuminate\Support\Collection does not specify its types: TKey, TValue
{
return $this->entities;
}

public function toArray(): array
{
return array_filter([
Expand Down Expand Up @@ -358,6 +373,7 @@ public function toArray(): array
'left_chat_member' => $this->leftChatMember,
'web_app_data' => $this->webAppData,
'write_access_allowed' => $this->writeAccessAllowed?->toArray(),
'entities' => $this->entities->toArray(),
], fn ($value) => $value !== null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@
"title": "john_smith"
},
"photos": [],
"new_chat_members": []
"new_chat_members": [],
"entities": [
{
"type": "bot_command",
"offset": 0,
"length": 6
}
]
}
},
{
Expand All @@ -46,7 +53,8 @@
"title": "Bot Test Chat"
},
"photos": [],
"new_chat_members": []
"new_chat_members": [],
"entities": []
}
}
]
25 changes: 25 additions & 0 deletions tests/Support/TestEntitiesWebhookHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace DefStudio\Telegraph\Tests\Support;

use DefStudio\Telegraph\Handlers\WebhookHandler;
use Illuminate\Support\Stringable;

class TestEntitiesWebhookHandler extends WebhookHandler
{
protected function handleChatMessage(Stringable $text): void
{
/** @var \DefStudio\Telegraph\DTO\Entity $entity */
$entity = $this->message->entities()->first();

$fromText = $text->substr($entity->offset(), $entity->length());
$fromEntity = $entity->url();

$this->chat->html(implode('. ', [
'URL from text: ' . $fromText,
'URL from entity: ' . $fromEntity,
]))->send();
}
}
40 changes: 40 additions & 0 deletions tests/Unit/DTO/EntityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/** @noinspection PhpUnhandledExceptionInspection */

use DefStudio\Telegraph\DTO\Entity;
use DefStudio\Telegraph\DTO\Message;
use Illuminate\Support\Str;

it('export all properties to array', function () {
$dto = Message::fromArray([
'message_id' => 2,
'date' => now()->timestamp,
'entities' => [
[
'type' => 'url',
'offset' => 10,
'length' => 19,
'url' => 'https://example.com',
'user' => [
'id' => 1,
'is_bot' => true,
'first_name' => 'a',
'last_name' => 'b',
'username' => 'c',
'language_code' => 'd',
'is_premium' => false,
],
'language' => 'en',
'custom_emoji_id' => '12345',
],
],
]);

$array = $dto->entities()->first()->toArray();

$reflection = new ReflectionClass(Entity::class);
foreach ($reflection->getProperties() as $property) {
expect($array)->toHaveKey(Str::of($property->name)->snake());
}
});
8 changes: 8 additions & 0 deletions tests/Unit/DTO/MessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,14 @@
"web_app_name" => "test",
"from_attachment_menu" => true,
],
'entities' => [
[
'type' => 'url',
'offset' => 4,
'length' => 19,
'url' => 'https://example.com',
],
],
]);

$array = $dto->toArray();
Expand Down
30 changes: 30 additions & 0 deletions tests/Unit/Handlers/WebhookHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use DefStudio\Telegraph\Facades\Telegraph as Facade;
use DefStudio\Telegraph\Telegraph;
use DefStudio\Telegraph\Tests\Support\TestEntitiesWebhookHandler;
use DefStudio\Telegraph\Tests\Support\TestWebhookHandler;
use Illuminate\Support\Facades\Config;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Expand Down Expand Up @@ -454,6 +455,35 @@
Facade::assertSent("New reaction is 👍:Old reaction is 🔥");
});

it('can handle a message entities', function () {
$bot = bot();
Facade::fake();

app(TestEntitiesWebhookHandler::class)->handle(webhook_message(TestEntitiesWebhookHandler::class, [
'message_id' => 123456,
'chat' => [
'id' => -123456789,
'type' => 'group',
'title' => 'Test chat',
],
'date' => 1646516736,
'text' => 'foo https://example.com bar',
'entities' => [
[
'type' => 'url',
'offset' => 4,
'length' => 19,
'url' => 'https://example.com',
],
],
]), $bot);

Facade::assertSent(implode('. ', [
'URL from text: https://example.com',
'URL from entity: https://example.com',
]));
});

it('does not crash on errors', function () {
$chat = chat();

Expand Down

0 comments on commit 55a3fe7

Please sign in to comment.