mirror of
https://github.com/BushlanovDev/max-bot-api-client-php.git
synced 2026-10-12 14:59:01 +00:00
Added BotRemovedFromChatUpdate & ChatTitleChangedUpdate & MessageChatCreatedUpdate & UserAddedToChatUpdate & UserRemovedFromChatUpdate
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
[](https://github.com/BushlanovDev/max-bot-api-client-php)
|
||||
[](LICENSE)
|
||||
|
||||
> [!CAUTION]
|
||||
> На мой взгляд `Max Messenger` является ни чем иным как малварью, созданной для слежки за гражданами РФ.
|
||||
> Настоятельно не рекомендую использовать свой настоящий номер телефона, на реальных устройствах, и для личной переписки.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Библиотека в стадии активной разработки.
|
||||
@@ -16,3 +19,51 @@
|
||||
|
||||
Откройте диалог с [MasterBot](https://max.ru/MasterBot), следуйте инструкциям и создайте нового бота. После создания
|
||||
бота MasterBot отправит вам токен.
|
||||
|
||||
## Реализованные методы
|
||||
|
||||
#### Bots
|
||||
|
||||
- `[x] GET /me` (`getBotInfo`) — *Получение информации о боте.*
|
||||
- `[ ] PATCH /me` (`editBotInfo`) — *Редактирование информации о боте.*
|
||||
|
||||
#### Chats
|
||||
|
||||
- `[ ] GET /chats` (`getChats`) — *Получение списка всех чатов бота.*
|
||||
- `[ ] GET /chats/{chatLink}` (`getChatByLink`) — *Получение информации о чате по ссылке.*
|
||||
- `[x] GET /chats/{chatId}` (`getChat`) — *Получение информации о чате по ID.*
|
||||
- `[ ] PATCH /chats/{chatId}` (`editChat`) — *Редактирование информации о чате.*
|
||||
- `[ ] DELETE /chats/{chatId}` (`deleteChat`) — *Удаление чата.*
|
||||
- `[ ] POST /chats/{chatId}/actions` (`sendAction`) — *Отправка действия в чат (например, "печатает...").*
|
||||
- `[ ] GET /chats/{chatId}/pin` (`getPinnedMessage`) — *Получение закрепленного сообщения.*
|
||||
- `[ ] PUT /chats/{chatId}/pin` (`pinMessage`) — *Закрепление сообщения.*
|
||||
- `[ ] DELETE /chats/{chatId}/pin` (`unpinMessage`) — *Открепление сообщения.*
|
||||
- `[ ] GET /chats/{chatId}/members/me` (`getMembership`) — *Получение информации о членстве бота в чате.*
|
||||
- `[ ] DELETE /chats/{chatId}/members/me` (`leaveChat`) — *Выход бота из чата.*
|
||||
- `[ ] GET /chats/{chatId}/members/admins` (`getAdmins`) — *Получение администраторов чата.*
|
||||
- `[ ] POST /chats/{chatId}/members/admins` (`postAdmins`) — *Назначение администраторов чата.*
|
||||
- `[ ] DELETE /chats/{chatId}/members/admins/{userId}` (`deleteAdmins`) — *Снятие прав администратора.*
|
||||
- `[ ] GET /chats/{chatId}/members` (`getMembers`) — *Получение участников чата.*
|
||||
- `[ ] POST /chats/{chatId}/members` (`addMembers`) — *Добавление участников в чат.*
|
||||
- `[ ] DELETE /chats/{chatId}/members` (`removeMember`) — *Удаление участника из чата.*
|
||||
|
||||
#### Subscriptions
|
||||
|
||||
- `[x] GET /subscriptions` (`getSubscriptions`) — *Получение списка Webhook-подписок.*
|
||||
- `[x] POST /subscriptions` (`subscribe`) — *Создание Webhook-подписки.*
|
||||
- `[x] DELETE /subscriptions` (`unsubscribe`) — *Удаление Webhook-подписки.*
|
||||
- `[x] GET /updates` (`getUpdates`) — *Получение обновлений через Long-Polling.*
|
||||
|
||||
#### Upload
|
||||
|
||||
- `[x] POST /uploads` (`getUploadUrl`) — *Получение URL для загрузки файла.*
|
||||
|
||||
#### Messages
|
||||
|
||||
- `[ ] GET /messages` (`getMessages`) — *Получение списка сообщений из чата.*
|
||||
- `[x] POST /messages` (`sendMessage`) — *Отправка сообщения.*
|
||||
- `[ ] PUT /messages` (`editMessage`) — *Редактирование сообщения.*
|
||||
- `[ ] DELETE /messages` (`deleteMessage`) — *Удаление сообщения.*
|
||||
- `[ ] GET /messages/{messageId}` (`getMessageById`) — *Получение сообщения по ID.*
|
||||
- `[ ] GET /videos/{videoToken}` (`getVideoAttachmentDetails`) — *Получение детальной информации о видео.*
|
||||
- `[ ] POST /answers` (`answerOnCallback`) — *Ответ на нажатие callback-кнопки.*
|
||||
|
||||
+143
-37
@@ -9,6 +9,7 @@ use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UploadType;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\ClientApiException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\NetworkException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\SecurityException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\SerializationException;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\AbstractAttachmentRequest;
|
||||
@@ -20,9 +21,11 @@ use BushlanovDev\MaxMessengerBot\Models\MessageLink;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use ReflectionException;
|
||||
use RuntimeException;
|
||||
|
||||
@@ -105,6 +108,146 @@ class Api
|
||||
return new WebhookHandler($this, $this->modelFactory, $secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an incoming webhook request and returns a single Update object.
|
||||
* This is an alternative to the event-driven WebhookHandler::handle() method,
|
||||
* allowing for manual processing of updates.
|
||||
*
|
||||
* @param string|null $secret The secret key to verify the request signature.
|
||||
* @param ServerRequestInterface|null $request The PSR-7 request object. If null, it's created from globals.
|
||||
*
|
||||
* @return AbstractUpdate The parsed update object (e.g., MessageCreatedUpdate).
|
||||
* @throws \ReflectionException
|
||||
* @throws SecurityException
|
||||
* @throws SerializationException
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function getWebhookUpdate(?string $secret = null, ?ServerRequestInterface $request = null): AbstractUpdate
|
||||
{
|
||||
return $this->createWebhookHandler($secret)->getUpdate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple way to process a single incoming webhook request using callbacks.
|
||||
* This method creates a WebhookHandler, registers the provided callbacks, and processes the request.
|
||||
*
|
||||
* @param array<string, callable> $handlers An associative array where keys are UpdateType string values
|
||||
* (e.g., UpdateType::MessageCreated->value) and values are handlers.
|
||||
* @param string|null $secret The secret key for request verification.
|
||||
* @param ServerRequestInterface|null $request The PSR-7 request object.
|
||||
*
|
||||
* @throws SecurityException
|
||||
* @throws SerializationException
|
||||
* @throws ReflectionException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function handleWebhooks(
|
||||
array $handlers,
|
||||
?string $secret = null,
|
||||
?ServerRequestInterface $request = null,
|
||||
): void {
|
||||
$webhookHandler = $this->createWebhookHandler($secret);
|
||||
|
||||
foreach ($handlers as $updateType => $callback) {
|
||||
$updateType = UpdateType::tryFrom($updateType);
|
||||
// @phpstan-ignore-next-line
|
||||
if ($updateType && is_callable($callback)) {
|
||||
$webhookHandler->addHandler($updateType, $callback);
|
||||
}
|
||||
}
|
||||
|
||||
$webhookHandler->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use this method for getting updates in case your bot is not subscribed to WebHook.
|
||||
* The method is based on long polling.
|
||||
*
|
||||
* @param int|null $limit Maximum number of updates to be retrieved (1-1000).
|
||||
* @param int|null $timeout Timeout in seconds for long polling (0-90).
|
||||
* @param int|null $marker Pass `null` to get updates you didn't get yet.
|
||||
* @param UpdateType[]|null $types Comma separated list of update types your bot want to receive.
|
||||
*
|
||||
* @return UpdateList
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function getUpdates(
|
||||
?int $limit = null,
|
||||
?int $timeout = null,
|
||||
?int $marker = null,
|
||||
?array $types = null,
|
||||
): UpdateList {
|
||||
$query = [
|
||||
'limit' => $limit,
|
||||
'timeout' => $timeout,
|
||||
'marker' => $marker,
|
||||
'types' => $types !== null ? implode(',', array_map(fn($type) => $type->value, $types)) : null,
|
||||
];
|
||||
|
||||
return $this->modelFactory->createUpdateList(
|
||||
$this->client->request(
|
||||
self::METHOD_GET,
|
||||
self::ACTION_UPDATES,
|
||||
array_filter($query, fn($value) => $value !== null),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a long-polling loop to process updates using callbacks.
|
||||
* This method will run indefinitely until the script is terminated.
|
||||
*
|
||||
* @param array<string, callable> $handlers An associative array where keys are UpdateType enums
|
||||
* and values are the corresponding handler functions.
|
||||
* @param int|null $timeout Timeout in seconds for long polling (0-90). Defaults to 90.
|
||||
* @param int|null $marker Pass `null` to get updates you didn't get yet.
|
||||
*/
|
||||
public function handleUpdates(array $handlers, ?int $timeout = null, ?int $marker = null): void
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
while (true) {
|
||||
try {
|
||||
$this->processUpdatesBatch($handlers, $timeout, $marker);
|
||||
} catch (NetworkException $e) {
|
||||
error_log("Network error: " . $e->getMessage());
|
||||
sleep(5);
|
||||
} catch (\Exception $e) {
|
||||
error_log("An error occurred: " . $e->getMessage());
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a single batch of updates. This is the core logic used by handleUpdates().
|
||||
* Useful for custom loop implementations or for testing.
|
||||
*
|
||||
* @param array<string, callable> $handlers An associative array of update handlers.
|
||||
* @param int|null $timeout Timeout for the getUpdates call.
|
||||
* @param int|null $marker The marker for which updates to fetch.
|
||||
*
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function processUpdatesBatch(array $handlers, ?int $timeout, ?int &$marker = null): void
|
||||
{
|
||||
$updateList = $this->getUpdates(timeout: $timeout, marker: $marker);
|
||||
|
||||
foreach ($updateList->updates as $update) {
|
||||
$handler = $handlers[$update->updateType->value] ?? null;
|
||||
if ($handler) {
|
||||
$handler($update, $this);
|
||||
}
|
||||
}
|
||||
|
||||
$marker = $updateList->marker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the current bot, identified by an access token.
|
||||
*
|
||||
@@ -330,41 +473,4 @@ class Api
|
||||
$this->client->request(self::METHOD_GET, self::ACTION_CHATS . '/' . $chatId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use this method for getting updates in case your bot is not subscribed to WebHook.
|
||||
* The method is based on long polling.
|
||||
*
|
||||
* @param int|null $limit Maximum number of updates to be retrieved (1-1000).
|
||||
* @param int|null $timeout Timeout in seconds for long polling (0-90).
|
||||
* @param int|null $marker Pass `null` to get updates you didn't get yet.
|
||||
* @param UpdateType[]|null $types Comma separated list of update types your bot want to receive.
|
||||
*
|
||||
* @return UpdateList
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function getUpdates(
|
||||
?int $limit = null,
|
||||
?int $timeout = null,
|
||||
?int $marker = null,
|
||||
?array $types = null,
|
||||
): UpdateList {
|
||||
$query = [
|
||||
'limit' => $limit,
|
||||
'timeout' => $timeout,
|
||||
'marker' => $marker,
|
||||
'types' => $types !== null ? implode(',', array_map(fn($type) => $type->value, $types)) : null,
|
||||
];
|
||||
|
||||
return $this->modelFactory->createUpdateList(
|
||||
$this->client->request(
|
||||
self::METHOD_GET,
|
||||
self::ACTION_UPDATES,
|
||||
array_filter($query, fn($value) => $value !== null),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,16 @@ use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\BotAddedToChatUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\BotRemovedFromChatUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\BotStartedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\ChatTitleChangedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCallbackUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageChatCreatedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCreatedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageEditedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageRemovedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\UserAddedToChatUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\UserRemovedFromChatUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use LogicException;
|
||||
use ReflectionException;
|
||||
@@ -162,7 +167,12 @@ class ModelFactory
|
||||
UpdateType::MessageEdited => MessageEditedUpdate::fromArray($data),
|
||||
UpdateType::MessageRemoved => MessageRemovedUpdate::fromArray($data),
|
||||
UpdateType::BotAdded => BotAddedToChatUpdate::fromArray($data),
|
||||
UpdateType::BotRemoved => BotRemovedFromChatUpdate::fromArray($data),
|
||||
UpdateType::UserAdded => UserAddedToChatUpdate::fromArray($data),
|
||||
UpdateType::UserRemoved => UserRemovedFromChatUpdate::fromArray($data),
|
||||
UpdateType::BotStarted => BotStartedUpdate::fromArray($data),
|
||||
UpdateType::ChatTitleChanged => ChatTitleChangedUpdate::fromArray($data),
|
||||
UpdateType::MessageChatCreated => MessageChatCreatedUpdate::fromArray($data),
|
||||
default => throw new LogicException(
|
||||
'Unknown or unsupported update type received: ' . ($data['update_type'] ?? 'none')
|
||||
),
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Updates;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
|
||||
/**
|
||||
* You will receive this update when the bot has been removed from a chat.
|
||||
*/
|
||||
final readonly class BotRemovedFromChatUpdate extends AbstractUpdate
|
||||
{
|
||||
/**
|
||||
* @param int $timestamp Unix-time when the event has occurred.
|
||||
* @param int $chatId Chat identifier the bot was removed from.
|
||||
* @param User $user User who removed the bot from the chat.
|
||||
* @param bool $isChannel Indicates whether the bot has been removed from a channel or not.
|
||||
*/
|
||||
public function __construct(
|
||||
int $timestamp,
|
||||
public int $chatId,
|
||||
public User $user,
|
||||
public bool $isChannel,
|
||||
) {
|
||||
parent::__construct(UpdateType::BotRemoved, $timestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Updates;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
|
||||
/**
|
||||
* Bot gets this type of update as soon as the title has been changed in a chat.
|
||||
*/
|
||||
final readonly class ChatTitleChangedUpdate extends AbstractUpdate
|
||||
{
|
||||
/**
|
||||
* @param int $timestamp Unix-time when the event has occurred.
|
||||
* @param int $chatId Chat identifier where the event has occurred.
|
||||
* @param User $user User who changed the title.
|
||||
* @param string $title The new title.
|
||||
*/
|
||||
public function __construct(
|
||||
int $timestamp,
|
||||
public int $chatId,
|
||||
public User $user,
|
||||
public string $title,
|
||||
) {
|
||||
parent::__construct(UpdateType::ChatTitleChanged, $timestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Updates;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Chat;
|
||||
|
||||
/**
|
||||
* Bot will get this update when a chat has been created as soon as
|
||||
* the first user clicked a `chat` button.
|
||||
*/
|
||||
final readonly class MessageChatCreatedUpdate extends AbstractUpdate
|
||||
{
|
||||
/**
|
||||
* @param int $timestamp Unix-time when the event has occurred.
|
||||
* @param Chat $chat The created chat.
|
||||
* @param string $messageId Message identifier where the button has been clicked.
|
||||
* @param string|null $startPayload Payload from the chat button.
|
||||
*/
|
||||
public function __construct(
|
||||
int $timestamp,
|
||||
public Chat $chat,
|
||||
public string $messageId,
|
||||
public ?string $startPayload,
|
||||
) {
|
||||
parent::__construct(UpdateType::MessageChatCreated, $timestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Updates;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
|
||||
/**
|
||||
* You will receive this update when a user has been added to a chat where the bot is an administrator.
|
||||
*/
|
||||
final readonly class UserAddedToChatUpdate extends AbstractUpdate
|
||||
{
|
||||
/**
|
||||
* @param int $timestamp Unix-time when the event has occurred.
|
||||
* @param int $chatId Chat identifier where the event has occurred.
|
||||
* @param User $user User who was added to the chat.
|
||||
* @param int|null $inviterId User who added the new user to the chat.
|
||||
* Can be `null` if the user joined via a link.
|
||||
* @param bool $isChannel Indicates whether the user has been added to a channel or not.
|
||||
*/
|
||||
public function __construct(
|
||||
int $timestamp,
|
||||
public int $chatId,
|
||||
public User $user,
|
||||
public ?int $inviterId,
|
||||
public bool $isChannel,
|
||||
) {
|
||||
parent::__construct(UpdateType::UserAdded, $timestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Updates;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
|
||||
/**
|
||||
* You will receive this update when a user has been removed from a chat where the bot is an administrator.
|
||||
*/
|
||||
final readonly class UserRemovedFromChatUpdate extends AbstractUpdate
|
||||
{
|
||||
/**
|
||||
* @param int $timestamp Unix-time when the event has occurred.
|
||||
* @param int $chatId Chat identifier where the event has occurred.
|
||||
* @param User $user User who was removed from the chat.
|
||||
* @param int|null $adminId Administrator who removed the user from the chat.
|
||||
* Can be `null` if the user left the chat themselves.
|
||||
* @param bool $isChannel Indicates whether the user has been removed from a channel or not.
|
||||
*/
|
||||
public function __construct(
|
||||
int $timestamp,
|
||||
public int $chatId,
|
||||
public User $user,
|
||||
public ?int $adminId,
|
||||
public bool $isChannel,
|
||||
) {
|
||||
parent::__construct(UpdateType::UserRemoved, $timestamp);
|
||||
}
|
||||
}
|
||||
+85
-5
@@ -100,6 +100,7 @@ final class WebhookHandler
|
||||
{
|
||||
return $this->addHandler(UpdateType::MessageRemoved, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenient alias for addHandler(UpdateType::BotAdded, $handler).
|
||||
*
|
||||
@@ -113,6 +114,45 @@ final class WebhookHandler
|
||||
return $this->addHandler(UpdateType::BotAdded, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenient alias for addHandler(UpdateType::BotRemoved, $handler).
|
||||
*
|
||||
* @param callable(Models\Updates\BotRemovedFromChatUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onBotRemoved(callable $handler): self
|
||||
{
|
||||
return $this->addHandler(UpdateType::BotRemoved, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenient alias for addHandler(UpdateType::UserAdded, $handler).
|
||||
*
|
||||
* @param callable(Models\Updates\UserAddedToChatUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onUserAdded(callable $handler): self
|
||||
{
|
||||
return $this->addHandler(UpdateType::UserAdded, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenient alias for addHandler(UpdateType::UserRemoved, $handler).
|
||||
*
|
||||
* @param callable(Models\Updates\UserRemovedFromChatUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onUserRemoved(callable $handler): self
|
||||
{
|
||||
return $this->addHandler(UpdateType::UserRemoved, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenient alias for addHandler(UpdateType::BotStarted, $handler).
|
||||
*
|
||||
@@ -126,6 +166,32 @@ final class WebhookHandler
|
||||
return $this->addHandler(UpdateType::BotStarted, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenient alias for addHandler(UpdateType::ChatTitleChanged, $handler).
|
||||
*
|
||||
* @param callable(Models\Updates\ChatTitleChangedUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onChatTitleChanged(callable $handler): self
|
||||
{
|
||||
return $this->addHandler(UpdateType::ChatTitleChanged, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenient alias for addHandler(UpdateType::MessageChatCreated, $handler).
|
||||
*
|
||||
* @param callable(Models\Updates\MessageChatCreatedUpdate, Api): void $handler
|
||||
*
|
||||
* @return $this
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onMessageChatCreated(callable $handler): self
|
||||
{
|
||||
return $this->addHandler(UpdateType::MessageChatCreated, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an incoming webhook request.
|
||||
* This is the main entry point. It reads the HTTP request body and headers,
|
||||
@@ -140,6 +206,24 @@ final class WebhookHandler
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function handle(?ServerRequestInterface $request = null): void
|
||||
{
|
||||
$this->dispatch($this->getUpdate($request));
|
||||
|
||||
http_response_code(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the raw request data and returns a typed Update object.
|
||||
*
|
||||
* @param ServerRequestInterface|null $request The Psr7 HTTP request to process.
|
||||
*
|
||||
* @return AbstractUpdate
|
||||
* @throws \ReflectionException
|
||||
* @throws SecurityException
|
||||
* @throws SerializationException
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function getUpdate(?ServerRequestInterface $request = null): AbstractUpdate
|
||||
{
|
||||
if ($request === null) {
|
||||
if (!class_exists(\GuzzleHttp\Psr7\ServerRequest::class)) {
|
||||
@@ -153,11 +237,7 @@ final class WebhookHandler
|
||||
$request = \GuzzleHttp\Psr7\ServerRequest::fromGlobals();
|
||||
}
|
||||
|
||||
|
||||
$update = $this->parseUpdate($request);
|
||||
$this->dispatch($update);
|
||||
|
||||
http_response_code(200);
|
||||
return $this->parseUpdate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,8 +28,13 @@ use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Sender;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\BotStartedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCreatedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
use BushlanovDev\MaxMessengerBot\WebhookHandler;
|
||||
use GuzzleHttp\Psr7\ServerRequest;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
@@ -58,6 +63,10 @@ use ReflectionClass;
|
||||
#[UsesClass(Chat::class)]
|
||||
#[UsesClass(UpdateList::class)]
|
||||
#[UsesClass(WebhookHandler::class)]
|
||||
#[UsesClass(User::class)]
|
||||
#[UsesClass(AbstractUpdate::class)]
|
||||
#[UsesClass(BotStartedUpdate::class)]
|
||||
#[UsesClass(MessageCreatedUpdate::class)]
|
||||
final class ApiTest extends TestCase
|
||||
{
|
||||
private MockObject&ClientApiInterface $clientMock;
|
||||
@@ -568,4 +577,179 @@ final class ApiTest extends TestCase
|
||||
$secretProperty = $reflection->getProperty('secret');
|
||||
$this->assertSame($secret, $secretProperty->getValue($webhookHandler));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function getWebhookUpdateCreatesHandlerAndReturnsUpdate(): void
|
||||
{
|
||||
$api = new Api('fake-token', $this->clientMock, $this->modelFactoryMock);
|
||||
|
||||
$payload = '{"update_type":"bot_started","timestamp":123,"chat_id":1,"user":{"user_id":1,"first_name":"Test","is_bot":false,"last_activity_time":123}}';
|
||||
$request = new ServerRequest('POST', '/webhook', [], $payload);
|
||||
|
||||
$expectedUpdate = new BotStartedUpdate(
|
||||
123,
|
||||
1,
|
||||
new User(1, 'Test', null, null, false, 123, null, null, null),
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createUpdate')
|
||||
->with(json_decode($payload, true))
|
||||
->willReturn($expectedUpdate);
|
||||
|
||||
$result = $api->getWebhookUpdate(null, $request);
|
||||
|
||||
$this->assertSame($expectedUpdate, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleWebhooksDispatchesCorrectHandler(): void
|
||||
{
|
||||
$api = new Api('fake-token', $this->clientMock, $this->modelFactoryMock);
|
||||
$secret = 'my-secret';
|
||||
|
||||
$payload = '{"update_type":"message_created","timestamp":123,"message":{"timestamp":1,"body":{"mid":"m1","seq":1},"recipient":{"chat_type":"dialog"}}}';
|
||||
$request = new \GuzzleHttp\Psr7\ServerRequest(
|
||||
'POST',
|
||||
'/webhook',
|
||||
['X-Max-Bot-Api-Secret' => $secret],
|
||||
$payload,
|
||||
);
|
||||
|
||||
$expectedUpdate = new MessageCreatedUpdate(
|
||||
123,
|
||||
Message::fromArray(
|
||||
['timestamp' => 1, 'body' => ['mid' => 'm1', 'seq' => 1], 'recipient' => ['chat_type' => 'dialog']]
|
||||
),
|
||||
null,
|
||||
);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createUpdate')
|
||||
->with(json_decode($payload, true))
|
||||
->willReturn($expectedUpdate);
|
||||
|
||||
$messageHandlerCallCount = 0;
|
||||
$messageHandlerCapturedUpdate = null;
|
||||
|
||||
$messageHandler = function (MessageCreatedUpdate $update, Api $receivedApi) use (
|
||||
&$messageHandlerCallCount,
|
||||
&$messageHandlerCapturedUpdate,
|
||||
) {
|
||||
$messageHandlerCallCount++;
|
||||
$messageHandlerCapturedUpdate = $update;
|
||||
};
|
||||
|
||||
$botStartedHandlerCallCount = 0;
|
||||
$botStartedHandler = function () use (&$botStartedHandlerCallCount) {
|
||||
$botStartedHandlerCallCount++;
|
||||
};
|
||||
|
||||
$handlers = [
|
||||
UpdateType::MessageCreated->value => $messageHandler,
|
||||
UpdateType::BotStarted->value => $botStartedHandler,
|
||||
];
|
||||
|
||||
$api->handleWebhooks($handlers, $secret, $request);
|
||||
|
||||
$this->assertSame(1, $messageHandlerCallCount, 'MessageCreated handler should be called once.');
|
||||
$this->assertSame(0, $botStartedHandlerCallCount, 'BotStarted handler should not be called.');
|
||||
$this->assertSame($expectedUpdate, $messageHandlerCapturedUpdate);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function processUpdatesBatchDispatchesHandlersAndUpdatesMarker(): void
|
||||
{
|
||||
$messageUpdate = new MessageCreatedUpdate(
|
||||
1,
|
||||
Message::fromArray(
|
||||
['timestamp' => 1, 'body' => ['mid' => 'm1', 'seq' => 1], 'recipient' => ['chat_type' => 'dialog']]
|
||||
),
|
||||
null,
|
||||
);
|
||||
$botStartedUpdate = new BotStartedUpdate(
|
||||
2, 123,
|
||||
User::fromArray(['user_id' => 1, 'first_name' => 'Test', 'is_bot' => false, 'last_activity_time' => 1]),
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
$messageHandlerCallCount = 0;
|
||||
$botStartedHandlerCallCount = 0;
|
||||
$handlers = [
|
||||
UpdateType::MessageCreated->value => function () use (&$messageHandlerCallCount) {
|
||||
$messageHandlerCallCount++;
|
||||
},
|
||||
UpdateType::BotStarted->value => function () use (&$botStartedHandlerCallCount) {
|
||||
$botStartedHandlerCallCount++;
|
||||
},
|
||||
];
|
||||
|
||||
$apiMock = $this->getMockBuilder(Api::class)
|
||||
->setConstructorArgs(['fake-token', $this->clientMock, $this->modelFactoryMock])
|
||||
->onlyMethods(['getUpdates'])
|
||||
->getMock();
|
||||
|
||||
$apiMock->expects($this->once())
|
||||
->method('getUpdates')
|
||||
->willReturn(new UpdateList([$messageUpdate, $botStartedUpdate], 12345));
|
||||
|
||||
$marker = null;
|
||||
|
||||
$apiMock->processUpdatesBatch($handlers, 90, $marker);
|
||||
|
||||
$this->assertSame(1, $messageHandlerCallCount, 'MessageCreated handler should have been called once.');
|
||||
$this->assertSame(1, $botStartedHandlerCallCount, 'BotStarted handler should have been called once.');
|
||||
$this->assertSame(12345, $marker, 'Marker should have been updated to the new value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int Counter for processUpdatesBatch method calls.
|
||||
*/
|
||||
private int $processUpdatesBatchCallCount = 0;
|
||||
|
||||
/**
|
||||
* @throws \Throwable We catch a base \Error, so we need to declare it here.
|
||||
*/
|
||||
#[Test]
|
||||
public function handleUpdatesLoopContinuesAfterException(): void
|
||||
{
|
||||
$this->processUpdatesBatchCallCount = 0;
|
||||
$handlers = [UpdateType::MessageCreated->value => fn() => null];
|
||||
|
||||
$apiMock = $this->getMockBuilder(Api::class)
|
||||
->setConstructorArgs(['fake-token', $this->clientMock, $this->modelFactoryMock])
|
||||
->onlyMethods(['processUpdatesBatch'])
|
||||
->getMock();
|
||||
|
||||
$apiMock->expects($this->any())
|
||||
->method('processUpdatesBatch')
|
||||
->willReturnCallback(function () {
|
||||
switch ($this->processUpdatesBatchCallCount++) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
throw new \BushlanovDev\MaxMessengerBot\Exceptions\NetworkException("Simulated network error");
|
||||
default:
|
||||
throw new \Error("LoopBreak");
|
||||
}
|
||||
});
|
||||
|
||||
$this->expectOutputRegex('/Network error: Simulated network error/');
|
||||
|
||||
try {
|
||||
$apiMock->handleUpdates($handlers);
|
||||
} catch (\Error $e) {
|
||||
$this->assertSame('LoopBreak', $e->getMessage());
|
||||
$this->assertSame(
|
||||
3,
|
||||
$this->processUpdatesBatchCallCount,
|
||||
'processUpdatesBatch should have been called 3 times.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Updates;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\BotRemovedFromChatUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(BotRemovedFromChatUpdate::class)]
|
||||
#[UsesClass(User::class)]
|
||||
final class BotRemovedFromChatUpdateTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedFromArrayAndSerialized(): void
|
||||
{
|
||||
$data = [
|
||||
'update_type' => 'bot_removed',
|
||||
'timestamp' => 1679100000,
|
||||
'chat_id' => 123456,
|
||||
'user' => [
|
||||
'user_id' => 555,
|
||||
'first_name' => 'Admin',
|
||||
'username' => 'chat_admin',
|
||||
'is_bot' => false,
|
||||
'last_activity_time' => 1679000000,
|
||||
'last_name' => null,
|
||||
'description' => null,
|
||||
'avatar_url' => null,
|
||||
'full_avatar_url' => null,
|
||||
],
|
||||
'is_channel' => false,
|
||||
];
|
||||
|
||||
$update = BotRemovedFromChatUpdate::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(BotRemovedFromChatUpdate::class, $update);
|
||||
$this->assertSame(UpdateType::BotRemoved, $update->updateType);
|
||||
$this->assertSame(1679100000, $update->timestamp);
|
||||
$this->assertSame(123456, $update->chatId);
|
||||
$this->assertFalse($update->isChannel);
|
||||
$this->assertInstanceOf(User::class, $update->user);
|
||||
$this->assertSame(555, $update->user->userId);
|
||||
$this->assertEquals($data, $update->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function canBeCreatedForChannel(): void
|
||||
{
|
||||
$data = [
|
||||
'update_type' => 'bot_removed',
|
||||
'timestamp' => 1679100001,
|
||||
'chat_id' => 777888999,
|
||||
'user' => [
|
||||
'user_id' => 556,
|
||||
'first_name' => 'AdminChan',
|
||||
'is_bot' => false,
|
||||
'last_activity_time' => 1679000001,
|
||||
],
|
||||
'is_channel' => true,
|
||||
];
|
||||
|
||||
$update = BotRemovedFromChatUpdate::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(BotRemovedFromChatUpdate::class, $update);
|
||||
$this->assertTrue($update->isChannel);
|
||||
$this->assertSame(777888999, $update->chatId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Updates;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\ChatTitleChangedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(ChatTitleChangedUpdate::class)]
|
||||
#[UsesClass(User::class)]
|
||||
final class ChatTitleChangedUpdateTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedFromArrayAndSerialized(): void
|
||||
{
|
||||
$data = [
|
||||
'update_type' => 'chat_title_changed',
|
||||
'timestamp' => 1680000000,
|
||||
'chat_id' => 12345,
|
||||
'user' => [
|
||||
'user_id' => 54321,
|
||||
'first_name' => 'John',
|
||||
'is_bot' => false,
|
||||
'last_activity_time' => 1679999999,
|
||||
'last_name' => null,
|
||||
'username' => null,
|
||||
'description' => null,
|
||||
'avatar_url' => null,
|
||||
'full_avatar_url' => null,
|
||||
],
|
||||
'title' => 'New Awesome Chat Title',
|
||||
];
|
||||
|
||||
$update = ChatTitleChangedUpdate::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(ChatTitleChangedUpdate::class, $update);
|
||||
$this->assertSame(UpdateType::ChatTitleChanged, $update->updateType);
|
||||
$this->assertSame(1680000000, $update->timestamp);
|
||||
$this->assertSame(12345, $update->chatId);
|
||||
$this->assertSame('New Awesome Chat Title', $update->title);
|
||||
$this->assertInstanceOf(User::class, $update->user);
|
||||
$this->assertSame(54321, $update->user->userId);
|
||||
$this->assertEquals($data, $update->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Updates;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Chat;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Image;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageChatCreatedUpdate;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(MessageChatCreatedUpdate::class)]
|
||||
#[UsesClass(Chat::class)]
|
||||
#[UsesClass(User::class)]
|
||||
#[UsesClass(Image::class)]
|
||||
final class MessageChatCreatedUpdateTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedWithAllData(): void
|
||||
{
|
||||
$data = [
|
||||
'update_type' => 'message_chat_created',
|
||||
'timestamp' => 1683000000,
|
||||
'chat' => [
|
||||
'chat_id' => 12345,
|
||||
'type' => 'chat',
|
||||
'status' => 'active',
|
||||
'last_event_time' => 1683000000,
|
||||
'participants_count' => 1,
|
||||
'is_public' => false,
|
||||
'title' => 'New Discussion',
|
||||
],
|
||||
'message_id' => 'mid.original.with.button',
|
||||
'start_payload' => 'payload_from_chat_button',
|
||||
];
|
||||
|
||||
$update = MessageChatCreatedUpdate::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(MessageChatCreatedUpdate::class, $update);
|
||||
$this->assertSame(UpdateType::MessageChatCreated, $update->updateType);
|
||||
$this->assertInstanceOf(Chat::class, $update->chat);
|
||||
$this->assertSame(12345, $update->chat->chatId);
|
||||
$this->assertSame('mid.original.with.button', $update->messageId);
|
||||
$this->assertSame('payload_from_chat_button', $update->startPayload);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function canBeCreatedWithNullStartPayload(): void
|
||||
{
|
||||
$data = [
|
||||
'update_type' => 'message_chat_created',
|
||||
'timestamp' => 1683000001,
|
||||
'chat' => [
|
||||
'chat_id' => 54321,
|
||||
'type' => 'chat',
|
||||
'status' => 'active',
|
||||
'last_event_time' => 1683000001,
|
||||
'participants_count' => 1,
|
||||
'is_public' => false,
|
||||
'title' => 'Another Discussion',
|
||||
],
|
||||
'message_id' => 'mid.another.message',
|
||||
'start_payload' => null,
|
||||
];
|
||||
|
||||
$update = MessageChatCreatedUpdate::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(MessageChatCreatedUpdate::class, $update);
|
||||
$this->assertNull($update->startPayload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Updates;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\UserAddedToChatUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(UserAddedToChatUpdate::class)]
|
||||
#[UsesClass(User::class)]
|
||||
final class UserAddedToChatUpdateTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedWhenInvitedByUser(): void
|
||||
{
|
||||
$data = [
|
||||
'update_type' => 'user_added',
|
||||
'timestamp' => 1681000000,
|
||||
'chat_id' => 12345,
|
||||
'user' => [
|
||||
'user_id' => 101,
|
||||
'first_name' => 'New',
|
||||
'last_name' => 'Member',
|
||||
'is_bot' => false,
|
||||
'last_activity_time' => 1680000000,
|
||||
'username' => null,
|
||||
'description' => null,
|
||||
'avatar_url' => null,
|
||||
'full_avatar_url' => null,
|
||||
],
|
||||
'inviter_id' => 202,
|
||||
'is_channel' => false,
|
||||
];
|
||||
|
||||
$update = UserAddedToChatUpdate::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(UserAddedToChatUpdate::class, $update);
|
||||
$this->assertSame(UpdateType::UserAdded, $update->updateType);
|
||||
$this->assertSame(12345, $update->chatId);
|
||||
$this->assertSame(202, $update->inviterId);
|
||||
$this->assertFalse($update->isChannel);
|
||||
$this->assertInstanceOf(User::class, $update->user);
|
||||
$this->assertSame(101, $update->user->userId);
|
||||
$this->assertEquals($data, $update->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function canBeCreatedWhenJoinedByLink(): void
|
||||
{
|
||||
$data = [
|
||||
'update_type' => 'user_added',
|
||||
'timestamp' => 1681000001,
|
||||
'chat_id' => 54321,
|
||||
'user' => [
|
||||
'user_id' => 303,
|
||||
'first_name' => 'Link',
|
||||
'last_name' => 'Joiner',
|
||||
'is_bot' => false,
|
||||
'last_activity_time' => 1680000001,
|
||||
'username' => null,
|
||||
'description' => null,
|
||||
'avatar_url' => null,
|
||||
'full_avatar_url' => null,
|
||||
],
|
||||
'inviter_id' => null,
|
||||
'is_channel' => true,
|
||||
];
|
||||
|
||||
$update = UserAddedToChatUpdate::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(UserAddedToChatUpdate::class, $update);
|
||||
$this->assertNull($update->inviterId);
|
||||
$this->assertTrue($update->isChannel);
|
||||
$this->assertSame(303, $update->user->userId);
|
||||
$this->assertEquals($data, $update->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Updates;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\UserRemovedFromChatUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(UserRemovedFromChatUpdate::class)]
|
||||
#[UsesClass(User::class)]
|
||||
final class UserRemovedFromChatUpdateTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedWhenRemovedByAdmin(): void
|
||||
{
|
||||
$data = [
|
||||
'update_type' => 'user_removed',
|
||||
'timestamp' => 1682000000,
|
||||
'chat_id' => 98765,
|
||||
'user' => [
|
||||
'user_id' => 111,
|
||||
'first_name' => 'Removed',
|
||||
'last_name' => 'User',
|
||||
'is_bot' => false,
|
||||
'last_activity_time' => 1681000000,
|
||||
'username' => null,
|
||||
'description' => null,
|
||||
'avatar_url' => null,
|
||||
'full_avatar_url' => null,
|
||||
],
|
||||
'admin_id' => 222,
|
||||
'is_channel' => false,
|
||||
];
|
||||
|
||||
$update = UserRemovedFromChatUpdate::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(UserRemovedFromChatUpdate::class, $update);
|
||||
$this->assertSame(UpdateType::UserRemoved, $update->updateType);
|
||||
$this->assertSame(98765, $update->chatId);
|
||||
$this->assertSame(222, $update->adminId);
|
||||
$this->assertFalse($update->isChannel);
|
||||
$this->assertInstanceOf(User::class, $update->user);
|
||||
$this->assertSame(111, $update->user->userId);
|
||||
$this->assertEquals($data, $update->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function canBeCreatedWhenUserLeft(): void
|
||||
{
|
||||
$data = [
|
||||
'update_type' => 'user_removed',
|
||||
'timestamp' => 1682000001,
|
||||
'chat_id' => 112233,
|
||||
'user' => [
|
||||
'user_id' => 444,
|
||||
'first_name' => 'Self',
|
||||
'last_name' => 'Remover',
|
||||
'is_bot' => false,
|
||||
'last_activity_time' => 1681000001,
|
||||
'username' => null,
|
||||
'description' => null,
|
||||
'avatar_url' => null,
|
||||
'full_avatar_url' => null,
|
||||
],
|
||||
'admin_id' => null,
|
||||
'is_channel' => true,
|
||||
];
|
||||
|
||||
$update = UserRemovedFromChatUpdate::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(UserRemovedFromChatUpdate::class, $update);
|
||||
$this->assertNull($update->adminId);
|
||||
$this->assertTrue($update->isChannel);
|
||||
$this->assertSame(444, $update->user->userId);
|
||||
$this->assertEquals($data, $update->toArray());
|
||||
}
|
||||
}
|
||||
@@ -204,4 +204,25 @@ final class WebhookHandlerTest extends TestCase
|
||||
|
||||
$this->assertSame($expectedUpdate, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function getUpdateParsesRequestAndReturnsUpdateObject(): void
|
||||
{
|
||||
$payload = $this->createValidUpdatePayload();
|
||||
$updateData = json_decode($payload, true);
|
||||
$expectedUpdate = $this->createRealUpdateObject($updateData);
|
||||
|
||||
$request = new ServerRequest('POST', '/webhook', [], $payload);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createUpdate')
|
||||
->with($updateData)
|
||||
->willReturn($expectedUpdate);
|
||||
|
||||
$webhookHandler = new WebhookHandler($this->apiMock, $this->modelFactoryMock);
|
||||
$result = $webhookHandler->getUpdate($request);
|
||||
|
||||
$this->assertSame($expectedUpdate, $result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user