Add comments API

Schema 0.0.33 adds comments to channel posts:

- GET    /messages/{messageId}/comments               getComments()
- GET    /messages/{messageId}/comments/{commentId}   getCommentById()
- POST   /messages/{messageId}/comments               sendComment()
- PUT    /messages/{messageId}/comments               editComment()
- DELETE /messages/{messageId}/comments               deleteComment()

Comments get their own models (CommentMessage, CommentMessageBody,
CommentLinkedMessage): unlike Message they have no attachments and
no public URL. README coverage map and docs updated.
This commit is contained in:
Timofey
2026-09-28 00:17:27 +05:00
parent 1bfeb17a15
commit c8f3b8e33d
10 changed files with 677 additions and 0 deletions
+8
View File
@@ -225,6 +225,14 @@ $handler->handle();
- [x] `GET /videos/{videoToken}` (`getVideoAttachmentDetails`) - [*Получение детальной информации о видео.*](./docs/README.md#Получение-детальной-информации-о-видео)
- [x] `POST /answers` (`answerOnCallback`) - [*Ответ на нажатие callback-кнопки.*](./docs/README.md#Ответ-на-нажатие-callback-кнопки)
#### Comments
- [x] `GET /messages/{messageId}/comments` (`getComments`) - [*Получение комментариев к посту.*](./docs/README.md#Получение-комментариев-к-посту)
- [x] `GET /messages/{messageId}/comments/{commentId}` (`getCommentById`) - [*Получение комментария по ID.*](./docs/README.md#Получение-комментария-по-ID)
- [x] `POST /messages/{messageId}/comments` (`sendComment`) - [*Отправка комментария.*](./docs/README.md#Отправка-комментария)
- [x] `PUT /messages/{messageId}/comments` (`editComment`) - [*Редактирование комментария.*](./docs/README.md#Редактирование-комментария)
- [x] `DELETE /messages/{messageId}/comments` (`deleteComment`) - [*Удаление комментария.*](./docs/README.md#Удаление-комментария)
## Лицензия
Данная библиотека распространяется под лицензией MIT - подробности см. в файле [LICENSE](LICENSE).
+60
View File
@@ -40,6 +40,12 @@
- `GET /messages/{messageId}` (`getMessageById`) - [*Получение сообщения по ID.*](#Получение-сообщения-по-ID)
- `GET /videos/{videoToken}` (`getVideoAttachmentDetails`) - [*Получение детальной информации о видео.*](#Получение-детальной-информации-о-видео)
- `POST /answers` (`answerOnCallback`) - [*Ответ на нажатие callback-кнопки.*](#Ответ-на-нажатие-callback-кнопки)
- [Комментарии](#Комментарии)
- `GET /messages/{messageId}/comments` (`getComments`) - [*Получение комментариев к посту.*](#Получение-комментариев-к-посту)
- `GET /messages/{messageId}/comments/{commentId}` (`getCommentById`) - [*Получение комментария по ID.*](#Получение-комментария-по-ID)
- `POST /messages/{messageId}/comments` (`sendComment`) - [*Отправка комментария.*](#Отправка-комментария)
- `PUT /messages/{messageId}/comments` (`editComment`) - [*Редактирование комментария.*](#Редактирование-комментария)
- `DELETE /messages/{messageId}/comments` (`deleteComment`) - [*Удаление комментария.*](#Удаление-комментария)
- [Laravel](#Laravel)
- [Регистрация пакета](#Регистрация-пакета)
- [Настройка](#Настройка)
@@ -459,6 +465,60 @@ $api->answerOnCallback(
);
```
## Комментарии
Комментарии оставляют к постам в каналах. В отличие от сообщений, у комментария нет вложений и публичной ссылки.
Для некоторых методов бот должен быть администратором канала с нужными правами. События `comment_created`,
`comment_edited` и `comment_removed` приходят, только если у бота есть право `read_all_messages`.
### Получение комментариев к посту
Возвращает комментарии к посту, начиная с последнего.
```php
$comments = $api->getComments(
messageId: 'mid.post', // Идентификатор поста
commentIds: ['mid.1', 'mid.2'], // Идентификаторы нужных комментариев (необязательно)
before: 1678886400000, // Комментарии до этого времени (необязательно)
after: 1678880000000, // Комментарии после этого времени (необязательно)
count: 50, // Максимальное количество комментариев в ответе, 1-100 (необязательно)
);
```
### Получение комментария по ID
```php
$comment = $api->getCommentById('mid.post', 'mid.comment');
```
### Отправка комментария
```php
$comment = $api->sendComment(
messageId: 'mid.post', // Идентификатор поста
text: 'Спасибо за пост!', // Текст комментария
format: MessageFormat::Markdown, // Формат текста Markdown или HTML (необязательно)
link: new MessageLink(MessageLinkType::Reply, 'mid.comment'), // Ответ на другой комментарий (необязательно)
disableLinkPreview: false, // Не генерировать превью ссылок (необязательно)
);
```
### Редактирование комментария
```php
$result = $api->editComment(
messageId: 'mid.post',
commentId: 'mid.comment',
text: 'Исправленный текст',
);
```
### Удаление комментария
```php
$result = $api->deleteComment('mid.post', 'mid.comment');
```
## Laravel
### Регистрация пакета
+171
View File
@@ -27,6 +27,7 @@ use BushlanovDev\MaxMessengerBot\Models\ChatList;
use BushlanovDev\MaxMessengerBot\Models\ChatMember;
use BushlanovDev\MaxMessengerBot\Models\ChatMembersList;
use BushlanovDev\MaxMessengerBot\Models\ChatPatch;
use BushlanovDev\MaxMessengerBot\Models\CommentMessage;
use BushlanovDev\MaxMessengerBot\Models\Message;
use BushlanovDev\MaxMessengerBot\Models\MessageLink;
use BushlanovDev\MaxMessengerBot\Models\Result;
@@ -77,6 +78,8 @@ class Api
private const string ACTION_UPDATES = '/updates';
private const string ACTION_ANSWERS = '/answers';
private const string ACTION_VIDEO_DETAILS = '/videos/%s';
private const string ACTION_MESSAGE_COMMENTS = '/messages/%s/comments';
private const string ACTION_MESSAGE_COMMENT = '/messages/%s/comments/%s';
private const int RESUMABLE_UPLOAD_THRESHOLD_BYTES = 10 * 1024 * 1024; // 10 MB
@@ -861,6 +864,153 @@ class Api
);
}
/**
* Returns comments to a post in a channel. Comments are traversed in reverse order, the latest first.
*
* @param string $messageId Identifier (`mid`) of the commented post.
* @param string[]|null $commentIds Identifiers of the comments to get.
* @param int|null $before Comments before this Unix timestamp (ms).
* @param int|null $after Comments after this Unix timestamp (ms).
* @param int|null $count Maximum amount of comments in the response (1-100, default 50).
*
* @return CommentMessage[]
* @throws ClientApiException
* @throws NetworkException
* @throws ReflectionException
* @throws SerializationException
*/
public function getComments(
string $messageId,
?array $commentIds = null,
?int $before = null,
?int $after = null,
?int $count = null,
): array {
$query = [
'comment_ids' => $commentIds !== null ? implode(',', $commentIds) : null,
'before' => $before,
'after' => $after,
'count' => $count,
];
$response = $this->client->request(
self::METHOD_GET,
sprintf(self::ACTION_MESSAGE_COMMENTS, $messageId),
array_filter($query, fn($value) => $value !== null),
);
return $this->modelFactory->createCommentMessages($response);
}
/**
* Returns a single comment to a post in a channel.
*
* @param string $messageId Identifier (`mid`) of the commented post.
* @param string $commentId Identifier (`mid`) of the comment.
*
* @return CommentMessage
* @throws ClientApiException
* @throws NetworkException
* @throws ReflectionException
* @throws SerializationException
*/
public function getCommentById(string $messageId, string $commentId): CommentMessage
{
return $this->modelFactory->createCommentMessage(
$this->client->request(
self::METHOD_GET,
sprintf(self::ACTION_MESSAGE_COMMENT, $messageId, $commentId),
)
);
}
/**
* Sends a comment to a post in a channel. Comments cannot have attachments.
*
* @param string $messageId Identifier (`mid`) of the commented post.
* @param string|null $text Comment text.
* @param MessageFormat|null $format Comment format.
* @param MessageLink|null $link Link to a comment to reply to or forward.
* @param bool $disableLinkPreview If false, server will not generate media preview for links in text.
*
* @return CommentMessage
* @throws ClientApiException
* @throws NetworkException
* @throws ReflectionException
* @throws SerializationException
*/
public function sendComment(
string $messageId,
?string $text = null,
?MessageFormat $format = null,
?MessageLink $link = null,
bool $disableLinkPreview = false,
): CommentMessage {
return $this->modelFactory->createCommentMessageFromSendResponse(
$this->client->request(
self::METHOD_POST,
sprintf(self::ACTION_MESSAGE_COMMENTS, $messageId),
['disable_link_preview' => $disableLinkPreview],
$this->buildNewCommentBody($text, $format, $link),
)
);
}
/**
* Edits a comment to a post in a channel. Comments cannot have attachments.
*
* @param string $messageId Identifier (`mid`) of the commented post.
* @param string $commentId Identifier (`mid`) of the comment to edit.
* @param string|null $text New comment text.
* @param MessageFormat|null $format Formatting for the new text.
* @param MessageLink|null $link New link for the edited comment.
*
* @return Result
* @throws ClientApiException
* @throws NetworkException
* @throws ReflectionException
* @throws SerializationException
*/
public function editComment(
string $messageId,
string $commentId,
?string $text = null,
?MessageFormat $format = null,
?MessageLink $link = null,
): Result {
return $this->modelFactory->createResult(
$this->client->request(
self::METHOD_PUT,
sprintf(self::ACTION_MESSAGE_COMMENTS, $messageId),
['comment_id' => $commentId],
$this->buildNewCommentBody($text, $format, $link),
)
);
}
/**
* Deletes a comment to a post in a channel if the bot has permission to delete messages.
*
* @param string $messageId Identifier (`mid`) of the commented post.
* @param string $commentId Identifier (`mid`) of the comment to delete.
*
* @return Result
* @throws ClientApiException
* @throws NetworkException
* @throws ReflectionException
* @throws SerializationException
*/
public function deleteComment(string $messageId, string $commentId): Result
{
return $this->modelFactory->createResult(
$this->client->request(
self::METHOD_DELETE,
sprintf(self::ACTION_MESSAGE_COMMENTS, $messageId),
['comment_id' => $commentId],
)
);
}
/**
* Pins a message in a chat or channel.
*
@@ -1233,4 +1383,25 @@ class Api
return array_filter($body, fn($item) => $item !== null);
}
/**
* A helper to build the 'NewCommentBody' array structure: like 'NewMessageBody',
* but without attachments and notify.
*
* @param string|null $text
* @param MessageFormat|null $format
* @param MessageLink|null $link
*
* @return array<string, mixed>
*/
private function buildNewCommentBody(?string $text, ?MessageFormat $format, ?MessageLink $link): array
{
$body = [
'text' => $text,
'format' => $format?->value,
'link' => $link,
];
return array_filter($body, fn($item) => $item !== null);
}
}
+70
View File
@@ -38,6 +38,8 @@ use BushlanovDev\MaxMessengerBot\Models\Chat;
use BushlanovDev\MaxMessengerBot\Models\ChatList;
use BushlanovDev\MaxMessengerBot\Models\ChatMember;
use BushlanovDev\MaxMessengerBot\Models\ChatMembersList;
use BushlanovDev\MaxMessengerBot\Models\CommentMessage;
use BushlanovDev\MaxMessengerBot\Models\CommentMessageBody;
use BushlanovDev\MaxMessengerBot\Models\Markup\AbstractMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\EmphasizedMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\HeadingMarkup;
@@ -224,6 +226,74 @@ readonly class ModelFactory
: [];
}
/**
* Comment to a post in a channel.
*
* @param array<string, mixed> $data
*
* @return CommentMessage
* @throws ReflectionException
*/
public function createCommentMessage(array $data): CommentMessage
{
if (isset($data['body']) && is_array($data['body'])) {
$data['body'] = $this->createCommentMessageBody($data['body']);
}
if (isset($data['link']['message']) && is_array($data['link']['message'])) {
$data['link']['message'] = $this->createCommentMessageBody($data['link']['message']);
}
return CommentMessage::fromArray($data);
}
/**
* List of comments.
*
* @param array<string, mixed> $data
*
* @return CommentMessage[]
*/
public function createCommentMessages(array $data): array
{
return isset($data['messages']) && is_array($data['messages'])
? array_map([$this, 'createCommentMessage'], $data['messages'])
: [];
}
/**
* Creates a CommentMessage from the response of the sendComment endpoint.
*
* @param array<string, mixed> $data
*
* @return CommentMessage
* @throws ReflectionException
*/
public function createCommentMessageFromSendResponse(array $data): CommentMessage
{
return $this->createCommentMessage($data['message']);
}
/**
* Creates a CommentMessageBody from raw API data, handling polymorphic markup.
*
* @param array<string, mixed> $data
*
* @return CommentMessageBody
* @throws ReflectionException
*/
private function createCommentMessageBody(array $data): CommentMessageBody
{
if (isset($data['markup']) && is_array($data['markup'])) {
$data['markup'] = array_map(
[$this, 'createMarkupElement'],
$data['markup'],
);
}
return CommentMessageBody::fromArray($data);
}
/**
* Creates a MessageBody object from raw API data, handling polymorphic attachments and markup.
*
+27
View File
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace BushlanovDev\MaxMessengerBot\Models;
use BushlanovDev\MaxMessengerBot\Enums\MessageLinkType;
/**
* A forwarded or replied comment linked to a comment.
*/
final readonly class CommentLinkedMessage extends AbstractModel
{
/**
* @param MessageLinkType $type Type of linked message (forward or reply).
* @param CommentMessageBody $message The body of the original comment.
* @param UserWithPhoto|null $sender The sender of the original comment. Can be null if posted on behalf of a channel.
* @param int|null $chatId The chat where the comment was originally posted.
*/
public function __construct(
public MessageLinkType $type,
public CommentMessageBody $message,
public ?UserWithPhoto $sender,
public ?int $chatId,
) {
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace BushlanovDev\MaxMessengerBot\Models;
/**
* A comment to a post in a channel. Unlike Message, it has no public URL and no attachments.
*/
final readonly class CommentMessage extends AbstractModel
{
/**
* @param int $timestamp Unix-time when the comment was created.
* @param Recipient $recipient Comment recipient, always a channel; carries the commented post in $postId.
* @param CommentMessageBody $body Body of the comment.
* @param UserWithPhoto|null $sender User who sent the comment. Can be null if posted on behalf of a channel.
* @param CommentLinkedMessage|null $link Forwarded or replied comment.
* @param MessageStat|null $stat Comment statistics.
*/
public function __construct(
public int $timestamp,
public Recipient $recipient,
public CommentMessageBody $body,
public ?UserWithPhoto $sender,
public ?CommentLinkedMessage $link,
public ?MessageStat $stat,
) {
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace BushlanovDev\MaxMessengerBot\Models;
use BushlanovDev\MaxMessengerBot\Models\Markup\AbstractMarkup;
/**
* Body of a comment. Unlike MessageBody, comments have no attachments.
*/
final readonly class CommentMessageBody extends AbstractModel
{
/**
* @param string $mid Unique identifier of the comment.
* @param int $seq Sequence identifier of the comment in the chat.
* @param string|null $text Comment text.
* @param AbstractMarkup[]|null $markup Comment text markup.
*/
public function __construct(
public string $mid,
public int $seq,
public ?string $text,
public ?array $markup,
) {
}
}
+155
View File
@@ -11,7 +11,9 @@ use BushlanovDev\MaxMessengerBot\ClientApiInterface;
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
use BushlanovDev\MaxMessengerBot\Enums\ChatAdminPermission;
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
use BushlanovDev\MaxMessengerBot\Enums\ChatType;
use BushlanovDev\MaxMessengerBot\Enums\MessageFormat;
use BushlanovDev\MaxMessengerBot\Enums\MessageLinkType;
use BushlanovDev\MaxMessengerBot\Enums\SenderAction;
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
use BushlanovDev\MaxMessengerBot\Enums\UploadType;
@@ -47,6 +49,9 @@ use BushlanovDev\MaxMessengerBot\Models\ChatMembersList;
use BushlanovDev\MaxMessengerBot\Models\ChatPatch;
use BushlanovDev\MaxMessengerBot\Models\Message;
use BushlanovDev\MaxMessengerBot\Models\MessageBody;
use BushlanovDev\MaxMessengerBot\Models\CommentMessage;
use BushlanovDev\MaxMessengerBot\Models\CommentMessageBody;
use BushlanovDev\MaxMessengerBot\Models\MessageLink;
use BushlanovDev\MaxMessengerBot\Models\Recipient;
use BushlanovDev\MaxMessengerBot\Models\Result;
use BushlanovDev\MaxMessengerBot\Models\UserWithPhoto;
@@ -86,6 +91,9 @@ use RuntimeException;
#[UsesClass(Message::class)]
#[UsesClass(MessageBody::class)]
#[UsesClass(Recipient::class)]
#[UsesClass(CommentMessage::class)]
#[UsesClass(CommentMessageBody::class)]
#[UsesClass(MessageLink::class)]
#[UsesClass(UserWithPhoto::class)]
#[UsesClass(CallbackButton::class)]
#[UsesClass(InlineKeyboardAttachmentRequestPayload::class)]
@@ -1332,6 +1340,153 @@ final class ApiTest extends TestCase
$this->assertSame($expectedMessages, $result);
}
#[Test]
public function getCommentsCallsClientWithAllParameters(): void
{
$expectedQuery = [
'comment_ids' => 'mid.c1,mid.c2',
'before' => 1678886400000,
'after' => 1678880000000,
'count' => 20,
];
$rawResponse = ['messages' => [$this->commentData()]];
$expectedComments = [$this->comment()];
$this->clientMock->expects($this->once())
->method('request')
->with('GET', '/messages/mid.post/comments', $expectedQuery)
->willReturn($rawResponse);
$this->modelFactoryMock->expects($this->once())
->method('createCommentMessages')
->with($rawResponse)
->willReturn($expectedComments);
$result = $this->api->getComments('mid.post', ['mid.c1', 'mid.c2'], 1678886400000, 1678880000000, 20);
$this->assertSame($expectedComments, $result);
}
#[Test]
public function getCommentsOmitsEmptyParameters(): void
{
$this->clientMock->expects($this->once())
->method('request')
->with('GET', '/messages/mid.post/comments', [])
->willReturn(['messages' => []]);
$this->modelFactoryMock->expects($this->once())
->method('createCommentMessages')
->willReturn([]);
$this->assertSame([], $this->api->getComments('mid.post'));
}
#[Test]
public function getCommentByIdRequestsTheSingleComment(): void
{
$expectedComment = $this->comment();
$this->clientMock->expects($this->once())
->method('request')
->with('GET', '/messages/mid.post/comments/mid.comment')
->willReturn($this->commentData());
$this->modelFactoryMock->expects($this->once())
->method('createCommentMessage')
->with($this->commentData())
->willReturn($expectedComment);
$this->assertSame($expectedComment, $this->api->getCommentById('mid.post', 'mid.comment'));
}
#[Test]
public function sendCommentPostsNewCommentBody(): void
{
$link = new MessageLink(MessageLinkType::Reply, 'mid.other');
$rawResponse = ['message' => $this->commentData()];
$expectedComment = $this->comment();
$this->clientMock->expects($this->once())
->method('request')
->with(
'POST',
'/messages/mid.post/comments',
['disable_link_preview' => true],
['text' => 'Nice post', 'format' => 'markdown', 'link' => $link],
)
->willReturn($rawResponse);
$this->modelFactoryMock->expects($this->once())
->method('createCommentMessageFromSendResponse')
->with($rawResponse)
->willReturn($expectedComment);
$result = $this->api->sendComment('mid.post', 'Nice post', MessageFormat::Markdown, $link, true);
$this->assertSame($expectedComment, $result);
}
#[Test]
public function editCommentPutsNewTextForTheComment(): void
{
$expectedResult = new Result(true, null);
$this->clientMock->expects($this->once())
->method('request')
->with('PUT', '/messages/mid.post/comments', ['comment_id' => 'mid.comment'], ['text' => 'Edited'])
->willReturn(['success' => true]);
$this->modelFactoryMock->expects($this->once())
->method('createResult')
->with(['success' => true])
->willReturn($expectedResult);
$this->assertSame($expectedResult, $this->api->editComment('mid.post', 'mid.comment', 'Edited'));
}
#[Test]
public function deleteCommentDeletesByCommentId(): void
{
$expectedResult = new Result(true, null);
$this->clientMock->expects($this->once())
->method('request')
->with('DELETE', '/messages/mid.post/comments', ['comment_id' => 'mid.comment'])
->willReturn(['success' => true]);
$this->modelFactoryMock->expects($this->once())
->method('createResult')
->with(['success' => true])
->willReturn($expectedResult);
$this->assertSame($expectedResult, $this->api->deleteComment('mid.post', 'mid.comment'));
}
/**
* @return array<string, mixed>
*/
private function commentData(): array
{
return [
'timestamp' => 1,
'body' => ['mid' => 'mid.comment', 'seq' => 1, 'text' => 'Nice post'],
'recipient' => ['chat_type' => 'channel', 'chat_id' => -100, 'post_id' => 'mid.post'],
];
}
private function comment(): CommentMessage
{
return new CommentMessage(
1,
new Recipient(ChatType::Channel, null, -100, 'mid.post'),
new CommentMessageBody('mid.comment', 1, 'Nice post', null),
null,
null,
null,
);
}
#[Test]
public function getMessagesReturnsEmptyArrayForEmptyResponse(): void
{
+60
View File
@@ -29,6 +29,10 @@ use BushlanovDev\MaxMessengerBot\Models\Attachments\ShareAttachment;
use BushlanovDev\MaxMessengerBot\Models\BotCommand;
use BushlanovDev\MaxMessengerBot\Models\BotCommandsInfo;
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
use BushlanovDev\MaxMessengerBot\Models\Markup\QuoteMarkup;
use BushlanovDev\MaxMessengerBot\Models\CommentLinkedMessage;
use BushlanovDev\MaxMessengerBot\Models\CommentMessage;
use BushlanovDev\MaxMessengerBot\Models\CommentMessageBody;
use BushlanovDev\MaxMessengerBot\Models\Chat;
use BushlanovDev\MaxMessengerBot\Models\ChatList;
use BushlanovDev\MaxMessengerBot\Models\ChatMember;
@@ -67,6 +71,10 @@ use Psr\Log\LoggerInterface;
#[UsesClass(BotInfo::class)]
#[UsesClass(BotCommand::class)]
#[UsesClass(BotCommandsInfo::class)]
#[UsesClass(CommentMessage::class)]
#[UsesClass(CommentMessageBody::class)]
#[UsesClass(CommentLinkedMessage::class)]
#[UsesClass(QuoteMarkup::class)]
#[UsesClass(Result::class)]
#[UsesClass(Subscription::class)]
#[UsesClass(ArrayOf::class)]
@@ -198,6 +206,58 @@ final class ModelFactoryTest extends TestCase
$this->assertNull($botCommands->commands[1]->description);
}
#[Test]
public function createCommentMessageHydratesBodyMarkupAndLink(): void
{
$comment = $this->factory->createCommentMessage([
'timestamp' => 1,
'recipient' => ['chat_type' => 'channel', 'chat_id' => -100, 'post_id' => 'mid.post'],
'body' => [
'mid' => 'mid.comment',
'seq' => 3,
'text' => 'Quoted reply',
'markup' => [['type' => 'quote', 'from' => 0, 'length' => 6]],
],
'sender' => ['user_id' => 42, 'first_name' => 'Anna', 'is_bot' => false],
'link' => [
'type' => 'reply',
'message' => [
'mid' => 'mid.parent',
'seq' => 2,
'text' => 'Parent',
'markup' => [['type' => 'strong', 'from' => 0, 'length' => 6]],
],
],
]);
$this->assertInstanceOf(CommentMessage::class, $comment);
$this->assertSame('mid.post', $comment->recipient->postId);
$this->assertSame('Quoted reply', $comment->body->text);
$this->assertInstanceOf(QuoteMarkup::class, $comment->body->markup[0]);
$this->assertSame(42, $comment->sender?->userId);
$this->assertInstanceOf(CommentLinkedMessage::class, $comment->link);
$this->assertInstanceOf(StrongMarkup::class, $comment->link->message->markup[0]);
$this->assertNull($comment->stat);
}
#[Test]
public function createCommentMessagesAndSendResponseUnwrapComments(): void
{
$data = [
'timestamp' => 1,
'recipient' => ['chat_type' => 'channel', 'chat_id' => -100],
'body' => ['mid' => 'mid.comment', 'seq' => 1],
];
$list = $this->factory->createCommentMessages(['messages' => [$data, $data]]);
$sent = $this->factory->createCommentMessageFromSendResponse(['message' => $data]);
$this->assertCount(2, $list);
$this->assertContainsOnlyInstancesOf(CommentMessage::class, $list);
$this->assertSame([], $this->factory->createCommentMessages([]));
$this->assertSame('mid.comment', $sent->body->mid);
}
#[Test]
public function createBotInfoHandlesNullCommands(): void
{
+70
View File
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace BushlanovDev\MaxMessengerBot\Tests\Models;
use BushlanovDev\MaxMessengerBot\Enums\MessageLinkType;
use BushlanovDev\MaxMessengerBot\Models\CommentLinkedMessage;
use BushlanovDev\MaxMessengerBot\Models\CommentMessage;
use BushlanovDev\MaxMessengerBot\Models\CommentMessageBody;
use BushlanovDev\MaxMessengerBot\Models\MessageStat;
use BushlanovDev\MaxMessengerBot\Models\Recipient;
use BushlanovDev\MaxMessengerBot\Models\UserWithPhoto;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(CommentMessage::class)]
#[CoversClass(CommentMessageBody::class)]
#[CoversClass(CommentLinkedMessage::class)]
#[UsesClass(Recipient::class)]
#[UsesClass(UserWithPhoto::class)]
#[UsesClass(MessageStat::class)]
final class CommentMessageTest extends TestCase
{
#[Test]
public function canBeCreatedFromArrayWithAllData(): void
{
$comment = CommentMessage::fromArray([
'timestamp' => 1678886400000,
'recipient' => ['chat_type' => 'channel', 'chat_id' => -100, 'post_id' => 'mid.post'],
'body' => ['mid' => 'mid.comment', 'seq' => 7, 'text' => 'Agree', 'markup' => null],
'sender' => ['user_id' => 42, 'first_name' => 'Anna', 'is_bot' => false],
'link' => [
'type' => 'forward',
'message' => ['mid' => 'mid.original', 'seq' => 1, 'text' => 'Original', 'markup' => null],
'sender' => null,
'chat_id' => -200,
],
'stat' => ['views' => 10],
]);
$this->assertSame(1678886400000, $comment->timestamp);
$this->assertSame('mid.post', $comment->recipient->postId);
$this->assertSame('mid.comment', $comment->body->mid);
$this->assertSame(7, $comment->body->seq);
$this->assertSame('Agree', $comment->body->text);
$this->assertSame('Anna', $comment->sender?->firstName);
$this->assertSame(MessageLinkType::Forward, $comment->link?->type);
$this->assertSame('Original', $comment->link?->message->text);
$this->assertSame(-200, $comment->link?->chatId);
$this->assertSame(10, $comment->stat?->views);
}
#[Test]
public function commentOnBehalfOfChannelHasNoSender(): void
{
$comment = CommentMessage::fromArray([
'timestamp' => 1,
'recipient' => ['chat_type' => 'channel', 'chat_id' => -100],
'body' => ['mid' => 'mid.comment', 'seq' => 1],
]);
$this->assertNull($comment->sender);
$this->assertNull($comment->link);
$this->assertNull($comment->body->text);
$this->assertNull($comment->body->markup);
}
}