Added getChats & getChatByLink & deleteChat & sendAction

This commit is contained in:
Alex
2025-07-27 09:13:55 +03:00
parent f195f9868f
commit 67ce2958a6
8 changed files with 419 additions and 4 deletions
+8 -4
View File
@@ -30,12 +30,12 @@
#### Chats
- [ ] `GET /chats` (`getChats`) — *Получение списка всех чатов бота.*
- [ ] `GET /chats/{chatLink}` (`getChatByLink`) — *Получение информации о чате по ссылке.*
- [x] `GET /chats` (`getChats`) — *Получение списка всех чатов бота.*
- [x] `GET /chats/{chatLink}` (`getChatByLink`) — *Получение информации о чате по ссылке.*
- [x] `GET /chats/{chatId}` (`getChat`) — *Получение информации о чате по ID.*
- [ ] `PATCH /chats/{chatId}` (`editChat`) — *Редактирование информации о чате.*
- [ ] `DELETE /chats/{chatId}` (`deleteChat`) — *Удаление чата.*
- [ ] `POST /chats/{chatId}/actions` (`sendAction`) — *Отправка действия в чат (например, "печатает...").*
- [x] `DELETE /chats/{chatId}` (`deleteChat`) — *Удаление чата.*
- [x] `POST /chats/{chatId}/actions` (`sendAction`) — *Отправка действия в чат (например, "печатает...").*
- [ ] `GET /chats/{chatId}/pin` (`getPinnedMessage`) — *Получение закрепленного сообщения.*
- [ ] `PUT /chats/{chatId}/pin` (`pinMessage`) — *Закрепление сообщения.*
- [ ] `DELETE /chats/{chatId}/pin` (`unpinMessage`) — *Открепление сообщения.*
@@ -68,3 +68,7 @@
- [ ] `GET /messages/{messageId}` (`getMessageById`) — *Получение сообщения по ID.*
- [ ] `GET /videos/{videoToken}` (`getVideoAttachmentDetails`) — *Получение детальной информации о видео.*
- [ ] `POST /answers` (`answerOnCallback`) — *Ответ на нажатие callback-кнопки.*
## Лицензия
Данная библиотека распространяется по лицензии MIT - подробности см. в файле [LICENSE](LICENSE).
+98
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace BushlanovDev\MaxMessengerBot;
use BushlanovDev\MaxMessengerBot\Enums\MessageFormat;
use BushlanovDev\MaxMessengerBot\Enums\SenderAction;
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
use BushlanovDev\MaxMessengerBot\Enums\UploadType;
use BushlanovDev\MaxMessengerBot\Exceptions\ClientApiException;
@@ -19,6 +20,7 @@ use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\PhotoAttachmentRequ
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\VideoAttachmentRequest;
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
use BushlanovDev\MaxMessengerBot\Models\Chat;
use BushlanovDev\MaxMessengerBot\Models\ChatList;
use BushlanovDev\MaxMessengerBot\Models\Message;
use BushlanovDev\MaxMessengerBot\Models\MessageLink;
use BushlanovDev\MaxMessengerBot\Models\Result;
@@ -46,6 +48,7 @@ class Api
private const string METHOD_GET = 'GET';
private const string METHOD_POST = 'POST';
private const string METHOD_DELETE = 'DELETE';
// private const string METHOD_PATCH = 'PATCH';
private const string ACTION_ME = '/me';
private const string ACTION_SUBSCRIPTIONS = '/subscriptions';
@@ -477,4 +480,99 @@ class Api
$this->client->request(self::METHOD_GET, self::ACTION_CHATS . '/' . $chatId)
);
}
/**
* Returns chat/channel information by its public link or a dialog with a user by their username.
* The link should be prefixed with '@' or can be passed without it.
*
* @param string $chatLink Public chat link (e.g., '@mychannel') or username (e.g., '@john_doe').
*
* @return Chat
* @throws ClientApiException
* @throws NetworkException
* @throws ReflectionException
* @throws SerializationException
*/
public function getChatByLink(string $chatLink): Chat
{
return $this->modelFactory->createChat(
$this->client->request(
self::METHOD_GET,
self::ACTION_CHATS . '/' . $chatLink,
)
);
}
/**
* Returns information about chats that the bot participated in. The result is a paginated list.
*
* @param int|null $count Number of chats requested (1-100, default 50).
* @param int|null $marker Points to the next data page. Use null for the first page.
*
* @return ChatList
* @throws ClientApiException
* @throws NetworkException
* @throws ReflectionException
* @throws SerializationException
*/
public function getChats(?int $count = null, ?int $marker = null): ChatList
{
$query = [
'count' => $count,
'marker' => $marker,
];
return $this->modelFactory->createChatList(
$this->client->request(
self::METHOD_GET,
self::ACTION_CHATS,
array_filter($query, fn($value) => $value !== null),
)
);
}
/**
* Deletes a chat for all participants. The bot must have appropriate permissions.
*
* @param int $chatId Chat identifier to delete.
*
* @return Result
* @throws ClientApiException
* @throws NetworkException
* @throws ReflectionException
* @throws SerializationException
*/
public function deleteChat(int $chatId): Result
{
return $this->modelFactory->createResult(
$this->client->request(
self::METHOD_DELETE,
self::ACTION_CHATS . '/' . $chatId,
)
);
}
/**
* Sends a specific action to a chat, such as 'typing...'. This is used to show bot activity to the user.
*
* @param int $chatId The identifier of the target chat.
* @param SenderAction $action The action to be sent.
*
* @return Result
* @throws ClientApiException
* @throws NetworkException
* @throws ReflectionException
* @throws SerializationException
*/
public function sendAction(int $chatId, SenderAction $action): Result
{
return $this->modelFactory->createResult(
$this->client->request(
self::METHOD_POST,
self::ACTION_CHATS . '/' . $chatId . '/actions',
[],
['action' => $action->value],
)
);
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace BushlanovDev\MaxMessengerBot\Enums;
/**
* Represents the different actions a bot can send to a chat to indicate its status.
*/
enum SenderAction: string
{
case TypingOn = 'typing_on';
case SendingPhoto = 'sending_photo';
case SendingVideo = 'sending_video';
case SendingAudio = 'sending_audio';
case SendingFile = 'sending_file';
case MarkSeen = 'mark_seen';
}
+14
View File
@@ -7,6 +7,7 @@ namespace BushlanovDev\MaxMessengerBot;
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
use BushlanovDev\MaxMessengerBot\Models\Chat;
use BushlanovDev\MaxMessengerBot\Models\ChatList;
use BushlanovDev\MaxMessengerBot\Models\Message;
use BushlanovDev\MaxMessengerBot\Models\Result;
use BushlanovDev\MaxMessengerBot\Models\Subscription;
@@ -178,4 +179,17 @@ class ModelFactory
),
};
}
/**
* Information about chat list.
*
* @param array<string, mixed> $data
*
* @return ChatList
* @throws ReflectionException
*/
public function createChatList(array $data): ChatList
{
return ChatList::fromArray($data);
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace BushlanovDev\MaxMessengerBot\Models;
use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf;
/**
* Represents a paginated list of chats.
*/
final readonly class ChatList extends AbstractModel
{
/**
* @param Chat[] $chats List of requested chats.
* @param int|null $marker Reference to the next page of requested chats. Can be null if it's the last page.
*/
public function __construct(
#[ArrayOf(Chat::class)]
public array $chats,
public ?int $marker,
) {
}
}
+157
View File
@@ -10,6 +10,7 @@ use BushlanovDev\MaxMessengerBot\ClientApiInterface;
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
use BushlanovDev\MaxMessengerBot\Enums\MessageFormat;
use BushlanovDev\MaxMessengerBot\Enums\SenderAction;
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
use BushlanovDev\MaxMessengerBot\Enums\UploadType;
use BushlanovDev\MaxMessengerBot\Exceptions\SerializationException;
@@ -34,6 +35,7 @@ use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\StickerAttachmentRe
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\VideoAttachmentRequest;
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
use BushlanovDev\MaxMessengerBot\Models\Chat;
use BushlanovDev\MaxMessengerBot\Models\ChatList;
use BushlanovDev\MaxMessengerBot\Models\Message;
use BushlanovDev\MaxMessengerBot\Models\MessageBody;
use BushlanovDev\MaxMessengerBot\Models\Recipient;
@@ -98,6 +100,7 @@ use RuntimeException;
#[UsesClass(LocationAttachmentRequestPayload::class)]
#[UsesClass(ShareAttachmentRequest::class)]
#[UsesClass(ShareAttachmentRequestPayload::class)]
#[UsesClass(ChatList::class)]
final class ApiTest extends TestCase
{
use PHPMock;
@@ -1197,4 +1200,158 @@ final class ApiTest extends TestCase
$this->assertSame($expectedMessageObject, $result);
}
#[Test]
public function getChatsPassesAllParametersToClient(): void
{
$count = 30;
$marker = 12345;
$expectedQuery = ['count' => $count, 'marker' => $marker];
$rawResponse = ['chats' => [], 'marker' => 54321];
$expectedChatList = new ChatList([], 54321);
$this->clientMock->expects($this->once())
->method('request')
->with('GET', '/chats', $expectedQuery)
->willReturn($rawResponse);
$this->modelFactoryMock->expects($this->once())
->method('createChatList')
->with($rawResponse)
->willReturn($expectedChatList);
$result = $this->api->getChats($count, $marker);
$this->assertSame($expectedChatList, $result);
}
#[Test]
public function getChatsHandlesNullParametersCorrectly(): void
{
$expectedQuery = [];
$rawResponse = ['chats' => [], 'marker' => null];
$expectedChatList = new ChatList([], null);
$this->clientMock->expects($this->once())
->method('request')
->with('GET', '/chats', $expectedQuery)
->willReturn($rawResponse);
$this->modelFactoryMock->expects($this->once())
->method('createChatList')
->with($rawResponse)
->willReturn($expectedChatList);
$result = $this->api->getChats(null, null);
$this->assertSame($expectedChatList, $result);
}
#[Test]
public function getChatByLinkCallsClientAndFactoryCorrectly(): void
{
$chatLink = '@test_channel';
$rawResponseData = [
'chat_id' => 987,
'type' => 'channel',
'status' => 'active',
'last_event_time' => 1,
'participants_count' => 100,
'is_public' => true,
'title' => 'Test Channel',
];
$expectedChatObject = Chat::fromArray($rawResponseData);
$this->clientMock
->expects($this->once())
->method('request')
->with('GET', '/chats/' . $chatLink)
->willReturn($rawResponseData);
$this->modelFactoryMock
->expects($this->once())
->method('createChat')
->with($rawResponseData)
->willReturn($expectedChatObject);
$result = $this->api->getChatByLink($chatLink);
$this->assertSame($expectedChatObject, $result);
}
#[Test]
public function getChatByLinkHandlesLinkWithoutAtSymbol(): void
{
$chatLink = 'test_channel_no_at';
$rawResponseData = [
'chat_id' => 987,
'type' => 'channel',
'status' => 'active',
'last_event_time' => 1,
'participants_count' => 100,
'is_public' => true,
'title' => 'Test Channel',
];
$expectedChatObject = Chat::fromArray($rawResponseData);
$this->clientMock->method('request')->willReturn($rawResponseData);
$this->modelFactoryMock->method('createChat')->willReturn($expectedChatObject);
$this->api->getChatByLink($chatLink);
$this->expectNotToPerformAssertions();
}
#[Test]
public function deleteChatCallsClientAndFactoryCorrectly(): void
{
$chatId = 123456789;
$rawResponseData = ['success' => true, 'message' => null];
$expectedResultObject = new Result(true, null);
$this->clientMock
->expects($this->once())
->method('request')
->with(self::equalTo('DELETE'), self::equalTo('/chats/' . $chatId))
->willReturn($rawResponseData);
$this->modelFactoryMock
->expects($this->once())
->method('createResult')
->with($rawResponseData)
->willReturn($expectedResultObject);
$result = $this->api->deleteChat($chatId);
$this->assertSame($expectedResultObject, $result);
$this->assertTrue($result->success);
}
#[Test]
public function sendActionCallsClientCorrectly(): void
{
$chatId = 12345;
$action = SenderAction::TypingOn;
$uri = '/chats/' . $chatId . '/actions';
$expectedBody = ['action' => 'typing_on'];
$rawResponse = ['success' => true];
$expectedResult = new Result(true, null);
$this->clientMock
->expects($this->once())
->method('request')
->with(self::equalTo('POST'), self::equalTo($uri), self::equalTo([]), self::equalTo($expectedBody))
->willReturn($rawResponse);
$this->modelFactoryMock
->expects($this->once())
->method('createResult')
->with($rawResponse)
->willReturn($expectedResult);
$result = $this->api->sendAction($chatId, $action);
$this->assertSame($expectedResult, $result);
}
}
+37
View File
@@ -10,6 +10,7 @@ use BushlanovDev\MaxMessengerBot\ModelFactory;
use BushlanovDev\MaxMessengerBot\Models\BotCommand;
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
use BushlanovDev\MaxMessengerBot\Models\Chat;
use BushlanovDev\MaxMessengerBot\Models\ChatList;
use BushlanovDev\MaxMessengerBot\Models\Image;
use BushlanovDev\MaxMessengerBot\Models\Message;
use BushlanovDev\MaxMessengerBot\Models\MessageBody;
@@ -48,6 +49,7 @@ use PHPUnit\Framework\TestCase;
#[UsesClass(Image::class)]
#[UsesClass(ChatTitleChangedUpdate::class)]
#[UsesClass(MessageChatCreatedUpdate::class)]
#[UsesClass(ChatList::class)]
final class ModelFactoryTest extends TestCase
{
private ModelFactory $factory;
@@ -314,4 +316,39 @@ final class ModelFactoryTest extends TestCase
$this->assertInstanceOf(ChatTitleChangedUpdate::class, $updateList->updates[2]);
$this->assertInstanceOf(MessageChatCreatedUpdate::class, $updateList->updates[3]);
}
#[Test]
public function createChatListDelegatesCreationToChatListModel(): void
{
$rawData = [
'chats' => [
[
'chat_id' => 101,
'type' => 'chat',
'status' => 'active',
'last_event_time' => 1,
'participants_count' => 5,
'is_public' => false,
],
[
'chat_id' => 102,
'type' => 'dialog',
'status' => 'suspended',
'last_event_time' => 2,
'participants_count' => 2,
'is_public' => false,
],
],
'marker' => 98765,
];
$chatList = $this->factory->createChatList($rawData);
$this->assertInstanceOf(ChatList::class, $chatList);
$this->assertSame(98765, $chatList->marker);
$this->assertCount(2, $chatList->chats);
$this->assertInstanceOf(Chat::class, $chatList->chats[0]);
$this->assertSame(101, $chatList->chats[0]->chatId);
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace BushlanovDev\MaxMessengerBot\Tests\Models;
use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf;
use BushlanovDev\MaxMessengerBot\Models\Chat;
use BushlanovDev\MaxMessengerBot\Models\ChatList;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(ChatList::class)]
#[UsesClass(Chat::class)]
#[UsesClass(ArrayOf::class)]
final class ChatListTest extends TestCase
{
#[Test]
public function canBeCreatedFromArray(): void
{
$rawData = [
'chats' => [
[
'chat_id' => 123,
'type' => 'chat',
'status' => 'active',
'last_event_time' => 1,
'participants_count' => 10,
'is_public' => false,
],
[
'chat_id' => 456,
'type' => 'dialog',
'status' => 'active',
'last_event_time' => 2,
'participants_count' => 2,
'is_public' => false,
],
],
'marker' => 98765,
];
$chatList = ChatList::fromArray($rawData);
$this->assertInstanceOf(ChatList::class, $chatList);
$this->assertCount(2, $chatList->chats);
$this->assertInstanceOf(Chat::class, $chatList->chats[0]);
$this->assertSame(123, $chatList->chats[0]->chatId);
$this->assertSame(98765, $chatList->marker);
}
#[Test]
public function canBeCreatedWithEmptyChatsAndNullMarker(): void
{
$rawData = ['chats' => [], 'marker' => null];
$chatList = ChatList::fromArray($rawData);
$this->assertEmpty($chatList->chats);
$this->assertNull($chatList->marker);
}
}