mirror of
https://github.com/BushlanovDev/max-bot-api-client-php.git
synced 2026-08-19 21:42:56 +00:00
Compare commits
13 Commits
0.5.0-beta
..
0.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b00bae23a | |||
| ca13096ec2 | |||
| 68da594f89 | |||
| 62fbb6cf3b | |||
| 97d016055f | |||
| 13e7c3fa45 | |||
| 32e393e044 | |||
| 728536f064 | |||
| c5d55a5c0f | |||
| 51b105b847 | |||
| f5acd0ee88 | |||
| 8fa63fa10b | |||
| eb5f6c7bbe |
+4
-5
@@ -1,5 +1,4 @@
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.github export-ignore
|
||||
/phpunit.xml export-ignore
|
||||
/tests export-ignore
|
||||
/.* export-ignore
|
||||
/phpunit.xml export-ignore
|
||||
/phpstan.neon export-ignore
|
||||
/tests export-ignore
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
[](https://github.com/BushlanovDev/max-bot-api-client-php/actions)
|
||||
[](https://github.com/BushlanovDev/max-bot-api-client-php/actions)
|
||||
[](https://packagist.org/packages/bushlanov-dev/max-bot-api-client-php)
|
||||
[](https://github.com/BushlanovDev/max-bot-api-client-php)
|
||||
[](LICENSE)
|
||||
|
||||
@@ -9,65 +10,116 @@
|
||||
> На мой взгляд `Max Messenger` является ни чем иным как малварью, созданной для слежки за гражданами РФ. Настоятельно
|
||||
> не рекомендую использовать его на реальных устройствах, с настоящим номером телефона, и для личной переписки.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Библиотека в стадии активной разработки.
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
> Если вы новичок, то можете прочитать [официальную документацию](https://dev.max.ru/), написанную разработчиками Max
|
||||
> Если вы новичок, то можете прочитать [официальную документацию](https://dev.max.ru/), написанную разработчиками Max.
|
||||
|
||||
### Получение токена
|
||||
|
||||
Откройте диалог с [MasterBot](https://max.ru/MasterBot), следуйте инструкциям и создайте нового бота. После создания
|
||||
бота MasterBot отправит вам токен.
|
||||
|
||||
### Установка библиотеки
|
||||
|
||||
```bash
|
||||
composer require bushlanov-dev/max-bot-api-client-php
|
||||
```
|
||||
|
||||
### Использование
|
||||
|
||||
Отправка сообщения с клавиатурой
|
||||
|
||||
```php
|
||||
$api = new \BushlanovDev\MaxMessengerBot\Api('YOUR_BOT_API_TOKEN');
|
||||
|
||||
$api->sendMessage(
|
||||
userId: 123, // ID пользователя получателя сообщения
|
||||
chatId: 321, // Или ID чата, в который нужно отправить сообщение
|
||||
text: 'Привет!', // Текст сообщения, вы можете использовать HTML или Markdown
|
||||
attachments: [
|
||||
new InlineKeyboardAttachmentRequest([
|
||||
[new CallbackButton('Нажми меня!', 'payload_button1')],
|
||||
[new LinkButton('Нажми меня!', 'https://example.com')],
|
||||
]),
|
||||
],
|
||||
format: MessageFormat::Markdown, // Формат сообщения (Markdown или HTML)
|
||||
);
|
||||
```
|
||||
|
||||
Подписка на вэб хуки
|
||||
|
||||
```php
|
||||
$api->subscribe(
|
||||
url: 'https://example.com/webhook', // URL на который будут приходить хуки
|
||||
secret: 'super_secret', // Секретная фраза для проверки хуков
|
||||
updateTypes: [
|
||||
// Типы хуков которые вы хотите получать (либо ничего не указывать, чтобы получать все)
|
||||
UpdateType::BotStarted,
|
||||
UpdateType::MessageCreated,
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
Обработка хуков
|
||||
|
||||
```php
|
||||
$webhookHandler = $api->createWebhookHandler();
|
||||
|
||||
$webhookHandler->addHandler(UpdateType::BotStarted, function (BotStartedUpdate $update, Api $api) {
|
||||
$api->sendMessage(
|
||||
chatId: $update->chatId,
|
||||
text: 'Я запущен!',
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
## Реализованные методы
|
||||
|
||||
#### Bots
|
||||
|
||||
- [x] `GET /me` (`getBotInfo`) — *Получение информации о боте.*
|
||||
- [ ] `PATCH /me` (`editBotInfo`) — *Редактирование информации о боте.*
|
||||
- [x] `GET /me` (`getBotInfo`) - *Получение информации о боте.*
|
||||
- [x] `PATCH /me` (`editBotInfo`) - *Редактирование информации о боте.*
|
||||
|
||||
#### Chats
|
||||
|
||||
- [x] `GET /chats` (`getChats`) — *Получение списка всех чатов бота.*
|
||||
- [x] `GET /chats/{chatLink}` (`getChatByLink`) — *Получение информации о чате по ссылке.*
|
||||
- [x] `GET /chats/{chatId}` (`getChat`) — *Получение информации о чате по ID.*
|
||||
- [ ] `PATCH /chats/{chatId}` (`editChat`) — *Редактирование информации о чате.*
|
||||
- [x] `DELETE /chats/{chatId}` (`deleteChat`) — *Удаление чата.*
|
||||
- [x] `POST /chats/{chatId}/actions` (`sendAction`) — *Отправка действия в чат (например, "печатает...").*
|
||||
- [x] `GET /chats/{chatId}/pin` (`getPinnedMessage`) — *Получение закрепленного сообщения.*
|
||||
- [x] `PUT /chats/{chatId}/pin` (`pinMessage`) — *Закрепление сообщения.*
|
||||
- [x] `DELETE /chats/{chatId}/pin` (`unpinMessage`) — *Открепление сообщения.*
|
||||
- [x] `GET /chats/{chatId}/members/me` (`getMembership`) — *Получение информации о членстве бота в чате.*
|
||||
- [x] `DELETE /chats/{chatId}/members/me` (`leaveChat`) — *Выход бота из чата.*
|
||||
- [x] `GET /chats/{chatId}/members/admins` (`getAdmins`) — *Получение администраторов чата.*
|
||||
- [ ] `POST /chats/{chatId}/members/admins` (`postAdmins`) — *Назначение администраторов чата.*
|
||||
- [x] `DELETE /chats/{chatId}/members/admins/{userId}` (`deleteAdmins`) — *Снятие прав администратора.*
|
||||
- [x] `GET /chats/{chatId}/members` (`getMembers`) — *Получение участников чата.*
|
||||
- [ ] `POST /chats/{chatId}/members` (`addMembers`) — *Добавление участников в чат.*
|
||||
- [x] `DELETE /chats/{chatId}/members` (`removeMember`) — *Удаление участника из чата.*
|
||||
- [x] `GET /chats` (`getChats`) - *Получение списка всех чатов бота.*
|
||||
- [x] `GET /chats/{chatLink}` (`getChatByLink`) - *Получение информации о чате по ссылке.*
|
||||
- [x] `GET /chats/{chatId}` (`getChat`) - *Получение информации о чате по ID.*
|
||||
- [x] `PATCH /chats/{chatId}` (`editChat`) - *Редактирование информации о чате.*
|
||||
- [x] `DELETE /chats/{chatId}` (`deleteChat`) - *Удаление чата.*
|
||||
- [x] `POST /chats/{chatId}/actions` (`sendAction`) - *Отправка действия в чат (например, "печатает...").*
|
||||
- [x] `GET /chats/{chatId}/pin` (`getPinnedMessage`) - *Получение закрепленного сообщения.*
|
||||
- [x] `PUT /chats/{chatId}/pin` (`pinMessage`) - *Закрепление сообщения.*
|
||||
- [x] `DELETE /chats/{chatId}/pin` (`unpinMessage`) - *Открепление сообщения.*
|
||||
- [x] `GET /chats/{chatId}/members/me` (`getMembership`) - *Получение информации о членстве бота в чате.*
|
||||
- [x] `DELETE /chats/{chatId}/members/me` (`leaveChat`) - *Выход бота из чата.*
|
||||
- [x] `GET /chats/{chatId}/members/admins` (`getAdmins`) - *Получение администраторов чата.*
|
||||
- [x] `POST /chats/{chatId}/members/admins` (`addAdmins`) - *Назначение администраторов чата.*
|
||||
- [x] `DELETE /chats/{chatId}/members/admins/{userId}` (`deleteAdmins`) - *Снятие прав администратора.*
|
||||
- [x] `GET /chats/{chatId}/members` (`getMembers`) - *Получение участников чата.*
|
||||
- [x] `POST /chats/{chatId}/members` (`addMembers`) - *Добавление участников в чат.*
|
||||
- [x] `DELETE /chats/{chatId}/members` (`deleteMember`) - *Удаление участника из чата.*
|
||||
|
||||
#### Subscriptions
|
||||
|
||||
- [x] `GET /subscriptions` (`getSubscriptions`) — *Получение списка Webhook-подписок.*
|
||||
- [x] `POST /subscriptions` (`subscribe`) — *Создание Webhook-подписки.*
|
||||
- [x] `DELETE /subscriptions` (`unsubscribe`) — *Удаление Webhook-подписки.*
|
||||
- [x] `GET /updates` (`getUpdates`) — *Получение обновлений через Long-Polling.*
|
||||
- [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 для загрузки файла.*
|
||||
- [x] `POST /uploads` (`getUploadUrl`) - *Получение URL для загрузки файла.*
|
||||
|
||||
#### Messages
|
||||
|
||||
- [x] `GET /messages` (`getMessages`) — *Получение списка сообщений из чата.*
|
||||
- [x] `POST /messages` (`sendMessage`) — *Отправка сообщения.*
|
||||
- [ ] `PUT /messages` (`editMessage`) — *Редактирование сообщения.*
|
||||
- [x] `DELETE /messages` (`deleteMessage`) — *Удаление сообщения.*
|
||||
- [x] `GET /messages/{messageId}` (`getMessageById`) — *Получение сообщения по ID.*
|
||||
- [ ] `GET /videos/{videoToken}` (`getVideoAttachmentDetails`) — *Получение детальной информации о видео.*
|
||||
- [ ] `POST /answers` (`answerOnCallback`) — *Ответ на нажатие callback-кнопки.*
|
||||
- [x] `GET /messages` (`getMessages`) - *Получение списка сообщений из чата.*
|
||||
- [x] `POST /messages` (`sendMessage`) - *Отправка сообщения.*
|
||||
- [x] `PUT /messages` (`editMessage`) - *Редактирование сообщения.*
|
||||
- [x] `DELETE /messages` (`deleteMessage`) - *Удаление сообщения.*
|
||||
- [x] `GET /messages/{messageId}` (`getMessageById`) - *Получение сообщения по ID.*
|
||||
- [x] `GET /videos/{videoToken}` (`getVideoAttachmentDetails`) - *Получение детальной информации о видео.*
|
||||
- [x] `POST /answers` (`answerOnCallback`) - *Ответ на нажатие callback-кнопки.*
|
||||
|
||||
## Лицензия
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/guzzle": "^6.5.8||^7.0",
|
||||
"guzzlehttp/psr7": "^1.8||^2.0",
|
||||
"psr/log": "^3.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.0||^2.0"
|
||||
|
||||
+353
-22
@@ -19,10 +19,13 @@ use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\FileAttachmentReque
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\PhotoAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\VideoAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotPatch;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Chat;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatAdmin;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatMember;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatMembersList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatPatch;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\MessageLink;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
@@ -30,9 +33,12 @@ use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use BushlanovDev\MaxMessengerBot\Models\VideoAttachmentDetails;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use ReflectionException;
|
||||
use RuntimeException;
|
||||
|
||||
@@ -44,13 +50,16 @@ use RuntimeException;
|
||||
*/
|
||||
class Api
|
||||
{
|
||||
private const string API_BASE_URL = 'https://botapi.max.ru';
|
||||
public const string LIBRARY_VERSION = '0.9.0';
|
||||
|
||||
public const string API_VERSION = '0.0.6';
|
||||
|
||||
private const string API_BASE_URL = 'https://botapi.max.ru';
|
||||
|
||||
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 METHOD_PATCH = 'PATCH';
|
||||
private const string METHOD_PUT = 'PUT';
|
||||
|
||||
private const string ACTION_ME = '/me';
|
||||
@@ -65,25 +74,33 @@ class Api
|
||||
private const string ACTION_CHATS_MEMBERS_ADMINS_ID = '/chats/%d/members/admins/%d';
|
||||
private const string ACTION_CHATS_MEMBERS = '/chats/%d/members';
|
||||
private const string ACTION_UPDATES = '/updates';
|
||||
private const string ACTION_ANSWERS = '/answers';
|
||||
private const string ACTION_VIDEO_DETAILS = '/videos/%s';
|
||||
|
||||
private readonly ClientApiInterface $client;
|
||||
|
||||
private readonly ModelFactory $modelFactory;
|
||||
|
||||
private readonly LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* Api constructor.
|
||||
*
|
||||
* @param string $accessToken Your bot's access token from @MasterBot.
|
||||
* @param ClientApiInterface|null $client Http api client.
|
||||
* @param ModelFactory|null $modelFactory
|
||||
* @param LoggerInterface|null $logger
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(
|
||||
string $accessToken,
|
||||
?ClientApiInterface $client = null,
|
||||
?ModelFactory $modelFactory = null
|
||||
?ModelFactory $modelFactory = null,
|
||||
?LoggerInterface $logger = null,
|
||||
) {
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
|
||||
if ($client === null) {
|
||||
if (!class_exists(\GuzzleHttp\Client::class) || !class_exists(\GuzzleHttp\Psr7\HttpFactory::class)) {
|
||||
throw new LogicException(
|
||||
@@ -92,7 +109,12 @@ class Api
|
||||
);
|
||||
}
|
||||
|
||||
$guzzle = new \GuzzleHttp\Client();
|
||||
$guzzle = new \GuzzleHttp\Client([
|
||||
'timeout' => 10,
|
||||
'connect_timeout' => 5,
|
||||
'read_timeout' => 10,
|
||||
'headers' => ['User-Agent' => 'max-bot-api-client-php/' . self::LIBRARY_VERSION . ' PHP/' . PHP_VERSION],
|
||||
]);
|
||||
$httpFactory = new \GuzzleHttp\Psr7\HttpFactory();
|
||||
$client = new Client(
|
||||
$accessToken,
|
||||
@@ -101,6 +123,7 @@ class Api
|
||||
$httpFactory,
|
||||
self::API_BASE_URL,
|
||||
self::API_VERSION,
|
||||
$this->logger,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,6 +131,25 @@ class Api
|
||||
$this->modelFactory = $modelFactory ?? new ModelFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request to the Max Bot API.
|
||||
*
|
||||
* @param string $method The HTTP method (GET, POST, PATCH, etc.).
|
||||
* @param string $uri The API endpoint (e.g., '/me', '/messages').
|
||||
* @param array<string, mixed> $queryParams Query parameters for the request.
|
||||
* @param array<string, mixed> $body The request body.
|
||||
*
|
||||
* @return array<string, mixed> The decoded JSON response as an associative array.
|
||||
* @throws ClientApiException for API-level errors (4xx, 5xx).
|
||||
* @throws NetworkException for network-related issues.
|
||||
* @throws SerializationException for JSON encoding/decoding failures.
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function request(string $method, string $uri, array $queryParams = [], array $body = []): array
|
||||
{
|
||||
return $this->client->request($method, $uri, $queryParams, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a WebhookHandler instance, pre-configured with the necessary dependencies.
|
||||
*
|
||||
@@ -118,7 +160,7 @@ class Api
|
||||
*/
|
||||
public function createWebhookHandler(?string $secret = null): WebhookHandler
|
||||
{
|
||||
return new WebhookHandler($this, $this->modelFactory, $secret);
|
||||
return new WebhookHandler($this, $this->modelFactory, $secret, $this->logger);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,10 +267,16 @@ class Api
|
||||
try {
|
||||
$this->processUpdatesBatch($handlers, $timeout, $marker);
|
||||
} catch (NetworkException $e) {
|
||||
error_log("Network error: " . $e->getMessage());
|
||||
$this->logger->error(
|
||||
'Long-polling network error: {message}',
|
||||
['message' => $e->getMessage(), 'exception' => $e],
|
||||
);
|
||||
sleep(5);
|
||||
} catch (\Exception $e) {
|
||||
error_log("An error occurred: " . $e->getMessage());
|
||||
$this->logger->error(
|
||||
'An error occurred during long-polling: {message}',
|
||||
['message' => $e->getMessage(), 'exception' => $e],
|
||||
);
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
@@ -348,7 +396,7 @@ class Api
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to a chat.
|
||||
* Sends a message to a chat or user.
|
||||
*
|
||||
* @param int|null $userId Fill this parameter if you want to send message to user.
|
||||
* @param int|null $chatId Fill this if you send message to chat.
|
||||
@@ -381,27 +429,76 @@ class Api
|
||||
'disable_link_preview' => $disableLinkPreview,
|
||||
];
|
||||
|
||||
$body = [
|
||||
'text' => $text,
|
||||
'format' => $format?->value,
|
||||
'notify' => $notify,
|
||||
'link' => $link,
|
||||
'attachments' => $attachments !== null ? array_map(
|
||||
fn(AbstractModel $attachment) => $attachment->toArray(),
|
||||
$attachments,
|
||||
) : null,
|
||||
];
|
||||
|
||||
$response = $this->client->request(
|
||||
self::METHOD_POST,
|
||||
self::ACTION_MESSAGES,
|
||||
array_filter($query, fn($item) => null !== $item),
|
||||
array_filter($body, fn($item) => null !== $item),
|
||||
$this->buildNewMessageBody($text, $attachments, $format, $link, $notify),
|
||||
);
|
||||
|
||||
return $this->modelFactory->createMessage($response['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to a user.
|
||||
*
|
||||
* @param int|null $userId Fill this parameter if you want to send message to user.
|
||||
* @param string|null $text Message text.
|
||||
* @param AbstractAttachmentRequest[]|null $attachments Message attachments.
|
||||
* @param MessageFormat|null $format Message format.
|
||||
* @param MessageLink|null $link Link to message.
|
||||
* @param bool $notify If false, chat participants would not be notified.
|
||||
* @param bool $disableLinkPreview If false, server will not generate media preview for links in text.
|
||||
*
|
||||
* @return Message
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function sendUserMessage(
|
||||
?int $userId = null,
|
||||
?string $text = null,
|
||||
?array $attachments = null,
|
||||
?MessageFormat $format = null,
|
||||
?MessageLink $link = null,
|
||||
bool $notify = true,
|
||||
bool $disableLinkPreview = false,
|
||||
): Message {
|
||||
return $this->sendMessage($userId, null, $text, $attachments, $format, $link, $notify, $disableLinkPreview);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to a chat.
|
||||
*
|
||||
* @param int|null $chatId Fill this if you send message to chat.
|
||||
* @param string|null $text Message text.
|
||||
* @param AbstractAttachmentRequest[]|null $attachments Message attachments.
|
||||
* @param MessageFormat|null $format Message format.
|
||||
* @param MessageLink|null $link Link to message.
|
||||
* @param bool $notify If false, chat participants would not be notified.
|
||||
* @param bool $disableLinkPreview If false, server will not generate media preview for links in text.
|
||||
*
|
||||
* @return Message
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function sendChatMessage(
|
||||
?int $chatId = null,
|
||||
?string $text = null,
|
||||
?array $attachments = null,
|
||||
?MessageFormat $format = null,
|
||||
?MessageLink $link = null,
|
||||
bool $notify = true,
|
||||
bool $disableLinkPreview = false,
|
||||
): Message {
|
||||
return $this->sendMessage(null, $chatId, $text, $attachments, $format, $link, $notify, $disableLinkPreview);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL for the subsequent file upload.
|
||||
*
|
||||
@@ -779,7 +876,7 @@ class Api
|
||||
[
|
||||
'message_id' => $messageId,
|
||||
'notify' => $notify,
|
||||
]
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -877,7 +974,7 @@ class Api
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function removeMember(int $chatId, int $userId, bool $block = false): Result
|
||||
public function deleteMember(int $chatId, int $userId, bool $block = false): Result
|
||||
{
|
||||
return $this->modelFactory->createResult(
|
||||
$this->client->request(
|
||||
@@ -890,4 +987,238 @@ class Api
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the administrators for a chat.
|
||||
*
|
||||
* @param int $chatId The identifier of the chat.
|
||||
* @param ChatAdmin[] $admins An array of ChatAdmin objects representing the users and their permissions.
|
||||
*
|
||||
* @return Result
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function addAdmins(int $chatId, array $admins): Result
|
||||
{
|
||||
return $this->modelFactory->createResult(
|
||||
$this->client->request(
|
||||
self::METHOD_POST,
|
||||
sprintf(self::ACTION_CHATS_MEMBERS_ADMINS, $chatId),
|
||||
[],
|
||||
['admins' => array_map(fn(ChatAdmin $admin) => $admin->toArray(), $admins)],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds members to a chat. The bot may require additional permissions.
|
||||
*
|
||||
* @param int $chatId The identifier of the chat.
|
||||
* @param int[] $userIds An array of user identifiers to add to the chat.
|
||||
*
|
||||
* @return Result
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function addMembers(int $chatId, array $userIds): Result
|
||||
{
|
||||
return $this->modelFactory->createResult(
|
||||
$this->client->request(
|
||||
self::METHOD_POST,
|
||||
sprintf(self::ACTION_CHATS_MEMBERS, $chatId),
|
||||
[],
|
||||
['user_ids' => $userIds],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an answer to a callback query. This should be called after a user clicks an inline button.
|
||||
*
|
||||
* @param string $callbackId The identifier of the callback query.
|
||||
* @param string|null $notification A short text notification to show to the user.
|
||||
* @param string|null $text If provided, the original message will be edited with this text.
|
||||
* @param AbstractAttachmentRequest[]|null $attachments New attachments for the edited message.
|
||||
* @param MessageLink|null $link New link for the edited message.
|
||||
* @param MessageFormat|null $format Formatting for the new message text.
|
||||
* @param bool $notify Notification setting for the edited message.
|
||||
*
|
||||
* @return Result
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function answerOnCallback(
|
||||
string $callbackId,
|
||||
?string $notification = null,
|
||||
?string $text = null,
|
||||
?array $attachments = null,
|
||||
?MessageLink $link = null,
|
||||
?MessageFormat $format = null,
|
||||
bool $notify = true,
|
||||
): Result {
|
||||
$answerBody = ['notification' => $notification];
|
||||
if ($text !== null || $attachments !== null || $link !== null) {
|
||||
$answerBody['message'] = $this->buildNewMessageBody($text, $attachments, $format, $link, $notify);
|
||||
}
|
||||
|
||||
return $this->modelFactory->createResult(
|
||||
$this->client->request(
|
||||
self::METHOD_POST,
|
||||
self::ACTION_ANSWERS,
|
||||
['callback_id' => $callbackId],
|
||||
array_filter($answerBody, fn($value) => $value !== null)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits a message that was previously sent by the bot.
|
||||
* Note on attachments:
|
||||
* - To leave attachments unchanged, pass `null` (default).
|
||||
* - To remove all attachments, pass an empty array `[]`.
|
||||
*
|
||||
* @param string $messageId The identifier of the message to edit.
|
||||
* @param string|null $text New message text.
|
||||
* @param AbstractAttachmentRequest[]|null $attachments New message attachments.
|
||||
* @param MessageFormat|null $format Formatting for the new message text.
|
||||
* @param MessageLink|null $link New link for the edited message.
|
||||
* @param bool $notify Notification setting for the edited message.
|
||||
*
|
||||
* @return Result
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function editMessage(
|
||||
string $messageId,
|
||||
?string $text = null,
|
||||
?array $attachments = null,
|
||||
?MessageFormat $format = null,
|
||||
?MessageLink $link = null,
|
||||
bool $notify = true,
|
||||
): Result {
|
||||
return $this->modelFactory->createResult(
|
||||
$this->client->request(
|
||||
self::METHOD_PUT,
|
||||
self::ACTION_MESSAGES,
|
||||
['message_id' => $messageId],
|
||||
$this->buildNewMessageBody($text, $attachments, $format, $link, $notify),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the bot info.
|
||||
*
|
||||
* Example: editBotInfo(new BotPatch(name: 'New Bot Name', description: null));
|
||||
*
|
||||
* @param BotPatch $botPatch
|
||||
*
|
||||
* @return BotInfo
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function editBotInfo(BotPatch $botPatch): BotInfo
|
||||
{
|
||||
return $this->modelFactory->createBotInfo(
|
||||
$this->client->request(
|
||||
self::METHOD_PATCH,
|
||||
self::ACTION_ME,
|
||||
[],
|
||||
$botPatch->toArray(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits chat info such as title, icon, etc.
|
||||
* Instantiate ChatPatch with named arguments for the fields you want to change.
|
||||
*
|
||||
* Example:
|
||||
* $patch = new ChatPatch(title: 'New Cool Title');
|
||||
* $api->editChat(12345, $patch);
|
||||
*
|
||||
* @param int $chatId The identifier of the chat to edit.
|
||||
* @param ChatPatch $chatPatch An object containing the fields to update.
|
||||
*
|
||||
* @return Chat
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function editChat(int $chatId, ChatPatch $chatPatch): Chat
|
||||
{
|
||||
return $this->modelFactory->createChat(
|
||||
$this->client->request(
|
||||
self::METHOD_PATCH,
|
||||
self::ACTION_CHATS . '/' . $chatId,
|
||||
[],
|
||||
$chatPatch->toArray(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns detailed information about a video attachment, including playback URLs.
|
||||
*
|
||||
* @param string $videoToken The token of the video attachment.
|
||||
*
|
||||
* @return VideoAttachmentDetails
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function getVideoAttachmentDetails(string $videoToken): VideoAttachmentDetails
|
||||
{
|
||||
return $this->modelFactory->createVideoAttachmentDetails(
|
||||
$this->client->request(
|
||||
self::METHOD_GET,
|
||||
sprintf(self::ACTION_VIDEO_DETAILS, $videoToken),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to build the 'NewMessageBody' array structure consistently.
|
||||
*
|
||||
* @param string|null $text
|
||||
* @param AbstractAttachmentRequest[]|null $attachments
|
||||
* @param MessageFormat|null $format
|
||||
* @param MessageLink|null $link
|
||||
* @param bool $notify
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function buildNewMessageBody(
|
||||
?string $text,
|
||||
?array $attachments,
|
||||
?MessageFormat $format,
|
||||
?MessageLink $link,
|
||||
bool $notify,
|
||||
): array {
|
||||
$body = [
|
||||
'text' => $text,
|
||||
'format' => $format?->value,
|
||||
'notify' => $notify,
|
||||
'link' => $link,
|
||||
'attachments' => $attachments !== null ? array_map(
|
||||
fn(AbstractModel $attachment) => $attachment->toArray(),
|
||||
$attachments,
|
||||
) : null,
|
||||
];
|
||||
|
||||
return array_filter($body, fn($item) => $item !== null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamFactoryInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* The low-level HTTP client responsible for communicating with the Max Bot API.
|
||||
@@ -34,6 +36,7 @@ final readonly class Client implements ClientApiInterface
|
||||
* @param StreamFactoryInterface $streamFactory A PSR-17 factory for creating request body streams.
|
||||
* @param string $baseUrl The base URL for API requests.
|
||||
* @param string|null $apiVersion The API version to use for requests.
|
||||
* @param LoggerInterface $logger
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
@@ -44,6 +47,7 @@ final readonly class Client implements ClientApiInterface
|
||||
private StreamFactoryInterface $streamFactory,
|
||||
private string $baseUrl,
|
||||
private ?string $apiVersion = null,
|
||||
private LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
if (empty($accessToken)) {
|
||||
throw new InvalidArgumentException('Access token cannot be empty.');
|
||||
@@ -60,6 +64,12 @@ final readonly class Client implements ClientApiInterface
|
||||
$queryParams['v'] = $this->apiVersion;
|
||||
}
|
||||
|
||||
$this->logger->debug('Sending API request', [
|
||||
'method' => $method,
|
||||
'url' => $this->baseUrl . $uri,
|
||||
'body' => $body,
|
||||
]);
|
||||
|
||||
$fullUrl = $this->baseUrl . $uri . '?' . http_build_query($queryParams);
|
||||
$request = $this->requestFactory->createRequest($method, $fullUrl);
|
||||
|
||||
@@ -79,6 +89,10 @@ final readonly class Client implements ClientApiInterface
|
||||
$response = $this->httpClient->sendRequest($request);
|
||||
} catch (ClientExceptionInterface $e) {
|
||||
// This catches network errors, DNS failures, timeouts, etc.
|
||||
$this->logger->error('Network exception during API request', [
|
||||
'message' => $e->getMessage(),
|
||||
'exception' => $e,
|
||||
]);
|
||||
throw new NetworkException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
@@ -86,6 +100,11 @@ final readonly class Client implements ClientApiInterface
|
||||
|
||||
$responseBody = (string)$response->getBody();
|
||||
|
||||
$this->logger->debug('Received API response', [
|
||||
'status' => $response->getStatusCode(),
|
||||
'body' => $responseBody,
|
||||
]);
|
||||
|
||||
// Handle successful but empty responses (e.g., from DELETE endpoints)
|
||||
if (empty($responseBody)) {
|
||||
// The API spec often returns {"success": true}, so we can simulate that
|
||||
@@ -161,6 +180,11 @@ final readonly class Client implements ClientApiInterface
|
||||
$errorCode = $data['code'] ?? 'unknown';
|
||||
$errorMessage = $data['message'] ?? 'An unknown error occurred.';
|
||||
|
||||
$this->logger->error('API error response received', [
|
||||
'status' => $statusCode,
|
||||
'body' => $responseBody,
|
||||
]);
|
||||
|
||||
throw match ($statusCode) {
|
||||
401 => new UnauthorizedException($errorMessage, $errorCode, $response),
|
||||
403 => new ForbiddenException($errorMessage, $errorCode, $response),
|
||||
|
||||
@@ -26,7 +26,7 @@ interface ClientApiInterface
|
||||
public function request(string $method, string $uri, array $queryParams = [], array $body = []): array;
|
||||
|
||||
/**
|
||||
* Performs a file download at the specified URL.
|
||||
* Performs a file upload at the specified URL.
|
||||
*
|
||||
* @param string $uri URL received from the download API.
|
||||
* @param resource|string $fileContents File content (stream resource or string).
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Enums;
|
||||
|
||||
enum AttachmentType: string
|
||||
enum AttachmentType: string
|
||||
{
|
||||
case Image = 'image';
|
||||
case Video = 'video';
|
||||
@@ -13,6 +13,8 @@ enum AttachmentType: string
|
||||
case Sticker = 'sticker';
|
||||
case Contact = 'contact';
|
||||
case InlineKeyboard = 'inline_keyboard';
|
||||
case ReplyKeyboard = 'reply_keyboard';
|
||||
case Location = 'location';
|
||||
case Share = 'share';
|
||||
case Data = 'data';
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Enums;
|
||||
|
||||
enum ButtonType: string
|
||||
enum InlineButtonType: string
|
||||
{
|
||||
case Callback = 'callback';
|
||||
case Link = 'link';
|
||||
@@ -12,4 +12,5 @@ enum ButtonType: string
|
||||
case RequestContact = 'request_contact';
|
||||
case OpenApp = 'open_app';
|
||||
case Message = 'message';
|
||||
case Chat = 'chat';
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Enums;
|
||||
|
||||
enum MarkupType: string
|
||||
{
|
||||
case Strong = 'strong';
|
||||
case Emphasized = 'emphasized';
|
||||
case Monospaced = 'monospaced';
|
||||
case Link = 'link';
|
||||
case Strikethrough = 'strikethrough';
|
||||
case Underline = 'underline';
|
||||
case UserMention = 'user_mention';
|
||||
case Heading = 'heading';
|
||||
case Highlighted = 'highlighted';
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Enums;
|
||||
|
||||
enum ReplyButtonType: string
|
||||
{
|
||||
case Message = 'message';
|
||||
case UserGeoLocation = 'user_geo_location';
|
||||
case UserContact = 'user_contact';
|
||||
}
|
||||
@@ -5,12 +5,7 @@ declare(strict_types=1);
|
||||
namespace BushlanovDev\MaxMessengerBot\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
class NetworkException extends RuntimeException
|
||||
{
|
||||
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,7 @@ declare(strict_types=1);
|
||||
namespace BushlanovDev\MaxMessengerBot\Exceptions;
|
||||
|
||||
use LogicException;
|
||||
use Throwable;
|
||||
|
||||
class SerializationException extends LogicException
|
||||
{
|
||||
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,51 @@ declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ReplyButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\AbstractAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\AudioAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\AbstractInlineButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\ChatButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\LinkButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\OpenAppButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\RequestContactButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\RequestGeoLocationButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\AbstractReplyButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendContactButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendGeoLocationButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendMessageButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\ContactAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\DataAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\FileAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\InlineKeyboardAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\LocationAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\PhotoAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\ReplyKeyboardAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\ShareAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\StickerAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\VideoAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Chat;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatMember;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatMembersList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\AbstractMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\EmphasizedMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\HeadingMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\HighlightedMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\LinkMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\MonospacedMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\StrikethroughMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\StrongMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\UnderlineMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\UserMentionMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\MessageBody;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
|
||||
@@ -27,6 +65,7 @@ use BushlanovDev\MaxMessengerBot\Models\Updates\MessageRemovedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\UserAddedToChatUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\UserRemovedFromChatUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use BushlanovDev\MaxMessengerBot\Models\VideoAttachmentDetails;
|
||||
use LogicException;
|
||||
use ReflectionException;
|
||||
|
||||
@@ -99,6 +138,10 @@ class ModelFactory
|
||||
*/
|
||||
public function createMessage(array $data): Message
|
||||
{
|
||||
if (isset($data['body']) && is_array($data['body'])) {
|
||||
$data['body'] = $this->createMessageBody($data['body']);
|
||||
}
|
||||
|
||||
return Message::fromArray($data);
|
||||
}
|
||||
|
||||
@@ -116,6 +159,118 @@ class ModelFactory
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a MessageBody object from raw API data, handling polymorphic attachments and markup.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return MessageBody
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function createMessageBody(array $data): MessageBody
|
||||
{
|
||||
if (isset($data['attachments']) && is_array($data['attachments'])) {
|
||||
$data['attachments'] = array_map(
|
||||
[$this, 'createAttachment'],
|
||||
$data['attachments'],
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($data['markup']) && is_array($data['markup'])) {
|
||||
$data['markup'] = array_map(
|
||||
[$this, 'createMarkupElement'],
|
||||
$data['markup'],
|
||||
);
|
||||
}
|
||||
|
||||
return MessageBody::fromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a specific Attachment model based on the 'type' field.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return AbstractAttachment
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function createAttachment(array $data): AbstractAttachment
|
||||
{
|
||||
$attachmentType = AttachmentType::tryFrom($data['type'] ?? '');
|
||||
if ($attachmentType === AttachmentType::ReplyKeyboard
|
||||
&& isset($data['buttons']) && is_array($data['buttons'])) {
|
||||
$data['buttons'] = array_map(
|
||||
fn($rowOfButtons) => array_map([$this, 'createReplyButton'], $rowOfButtons),
|
||||
$data['buttons'],
|
||||
);
|
||||
}
|
||||
|
||||
if ($attachmentType === AttachmentType::InlineKeyboard
|
||||
&& isset($data['payload']['buttons']) && is_array($data['payload']['buttons'])) {
|
||||
$data['payload']['buttons'] = array_map(
|
||||
fn($rowOfButtons) => array_map([$this, 'createInlineButton'], $rowOfButtons),
|
||||
$data['payload']['buttons']
|
||||
);
|
||||
}
|
||||
|
||||
return match ($attachmentType) {
|
||||
AttachmentType::Data => DataAttachment::fromArray($data),
|
||||
AttachmentType::Share => ShareAttachment::fromArray($data),
|
||||
AttachmentType::Image => PhotoAttachment::fromArray($data),
|
||||
AttachmentType::Video => VideoAttachment::fromArray($data),
|
||||
AttachmentType::Audio => AudioAttachment::fromArray($data),
|
||||
AttachmentType::File => FileAttachment::fromArray($data),
|
||||
AttachmentType::Sticker => StickerAttachment::fromArray($data),
|
||||
AttachmentType::Contact => ContactAttachment::fromArray($data),
|
||||
AttachmentType::InlineKeyboard => InlineKeyboardAttachment::fromArray($data),
|
||||
AttachmentType::ReplyKeyboard => ReplyKeyboardAttachment::fromArray($data),
|
||||
AttachmentType::Location => LocationAttachment::fromArray($data),
|
||||
default => throw new LogicException('Unknown or unsupported attachment type: ' . ($data['type'] ?? 'none')),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a specific ReplyButton model based on the 'type' field.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return AbstractReplyButton
|
||||
* @throws ReflectionException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function createReplyButton(array $data): AbstractReplyButton
|
||||
{
|
||||
return match (ReplyButtonType::tryFrom($data['type'] ?? '')) {
|
||||
ReplyButtonType::Message => SendMessageButton::fromArray($data),
|
||||
ReplyButtonType::UserContact => SendContactButton::fromArray($data),
|
||||
ReplyButtonType::UserGeoLocation => SendGeoLocationButton::fromArray($data),
|
||||
default => throw new LogicException(
|
||||
'Unknown or unsupported reply button type: ' . ($data['type'] ?? 'none')
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a specific InlineButton model based on the 'type' field.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return AbstractInlineButton
|
||||
* @throws ReflectionException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function createInlineButton(array $data): AbstractInlineButton
|
||||
{
|
||||
return match (InlineButtonType::tryFrom($data['type'] ?? '')) {
|
||||
InlineButtonType::Callback => CallbackButton::fromArray($data),
|
||||
InlineButtonType::Link => LinkButton::fromArray($data),
|
||||
InlineButtonType::RequestContact => RequestContactButton::fromArray($data),
|
||||
InlineButtonType::RequestGeoLocation => RequestGeoLocationButton::fromArray($data),
|
||||
InlineButtonType::Chat => ChatButton::fromArray($data),
|
||||
InlineButtonType::OpenApp => OpenAppButton::fromArray($data),
|
||||
default => throw new LogicException('Unknown or unsupported inline button type: ' . ($data['type'] ?? 'none')),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint you should upload to your binaries.
|
||||
*
|
||||
@@ -234,4 +389,43 @@ class ModelFactory
|
||||
{
|
||||
return ChatMembersList::fromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VideoAttachmentDetails object from raw API data.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return VideoAttachmentDetails
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function createVideoAttachmentDetails(array $data): VideoAttachmentDetails
|
||||
{
|
||||
return VideoAttachmentDetails::fromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a specific Markup model based on the 'type' field.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return AbstractMarkup
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function createMarkupElement(array $data): AbstractMarkup
|
||||
{
|
||||
return match (MarkupType::tryFrom($data['type'] ?? '')) {
|
||||
MarkupType::Strong => StrongMarkup::fromArray($data),
|
||||
MarkupType::Emphasized => EmphasizedMarkup::fromArray($data),
|
||||
MarkupType::Monospaced => MonospacedMarkup::fromArray($data),
|
||||
MarkupType::Strikethrough => StrikethroughMarkup::fromArray($data),
|
||||
MarkupType::Underline => UnderlineMarkup::fromArray($data),
|
||||
MarkupType::Heading => HeadingMarkup::fromArray($data),
|
||||
MarkupType::Highlighted => HighlightedMarkup::fromArray($data),
|
||||
MarkupType::Link => LinkMarkup::fromArray($data),
|
||||
MarkupType::UserMention => UserMentionMarkup::fromArray($data),
|
||||
default => throw new LogicException(
|
||||
'Unknown or unsupported markup type: ' . ($data['type'] ?? 'none')
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,65 @@ abstract readonly class AbstractModel
|
||||
return new static(...$constructorArgs); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($this);
|
||||
$properties = $reflectionClass->getProperties(ReflectionProperty::IS_PUBLIC);
|
||||
$result = [];
|
||||
|
||||
foreach ($properties as $property) {
|
||||
if (!$property->isInitialized($this)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$phpPropertyName = $property->getName();
|
||||
$value = $property->getValue($this);
|
||||
|
||||
$jsonKey = self::toSnakeCase($phpPropertyName);
|
||||
|
||||
$result[$jsonKey] = $this->convertValue($value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function toSnakeCase(string $input): string
|
||||
{
|
||||
return strtolower((string)preg_replace('/(?<!^)[A-Z]/', '_$0', $input));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
protected function convertValue(mixed $value): mixed
|
||||
{
|
||||
if ($value instanceof AbstractModel) {
|
||||
return $value->toArray();
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return array_map([$this, 'convertValue'], $value);
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
return $value->value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param ReflectionProperty $property
|
||||
@@ -59,6 +118,10 @@ abstract readonly class AbstractModel
|
||||
|
||||
$typeName = $type->getName();
|
||||
|
||||
if (is_object($value) && is_a($value, $typeName)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($type->isBuiltin()) {
|
||||
return match ($typeName) {
|
||||
'int' => (int)$value,
|
||||
@@ -74,7 +137,7 @@ abstract readonly class AbstractModel
|
||||
return $typeName::from($value);
|
||||
}
|
||||
|
||||
if (is_subclass_of($typeName, self::class)) {
|
||||
if (is_subclass_of($typeName, self::class) && is_array($value)) {
|
||||
return $typeName::fromArray($value);
|
||||
}
|
||||
|
||||
@@ -109,66 +172,10 @@ abstract readonly class AbstractModel
|
||||
}
|
||||
|
||||
if (is_subclass_of($itemClassName, self::class)) {
|
||||
return array_map(fn($item) => $itemClassName::fromArray($item), $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function toSnakeCase(string $input): string
|
||||
{
|
||||
return strtolower((string)preg_replace('/(?<!^)[A-Z]/', '_$0', $input));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($this);
|
||||
$properties = $reflectionClass->getProperties(ReflectionProperty::IS_PUBLIC);
|
||||
$result = [];
|
||||
|
||||
foreach ($properties as $property) {
|
||||
if (!$property->isInitialized($this)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$phpPropertyName = $property->getName();
|
||||
$value = $property->getValue($this);
|
||||
|
||||
$jsonKey = self::toSnakeCase($phpPropertyName);
|
||||
|
||||
$result[$jsonKey] = $this->convertValue($value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function convertValue(mixed $value): mixed
|
||||
{
|
||||
if ($value instanceof AbstractModel) {
|
||||
return $value->toArray();
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return array_map([$this, 'convertValue'], $value);
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
return $value->value;
|
||||
return array_map(
|
||||
fn($item) => is_a($item, $itemClassName) ? $item : $itemClassName::fromArray($item),
|
||||
$value,
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
/**
|
||||
* An abstract base for PATCH request models. It uses a variadic constructor
|
||||
* to capture named arguments, tracking which fields were explicitly set.
|
||||
*/
|
||||
abstract readonly class AbstractPatchModel extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @var array<int|string, mixed>
|
||||
*/
|
||||
protected array $patchData;
|
||||
|
||||
/**
|
||||
* Captures all passed named arguments into an associative array.
|
||||
*
|
||||
* @param mixed ...$params
|
||||
*/
|
||||
public function __construct(...$params)
|
||||
{
|
||||
$this->patchData = $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
final public function toArray(): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($this->patchData as $key => $value) {
|
||||
$jsonKey = $this->toSnakeCase((string)$key);
|
||||
$result[$jsonKey] = $this->convertValue($value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
/**
|
||||
* Represents a generic attachment received from the API.
|
||||
*/
|
||||
abstract readonly class AbstractAttachment extends AbstractModel
|
||||
{
|
||||
public function __construct(public AttachmentType $type)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\MediaAttachmentPayload;
|
||||
|
||||
final readonly class AudioAttachment extends AbstractAttachment
|
||||
{
|
||||
/**
|
||||
* @param MediaAttachmentPayload $payload Audio attachment payload.
|
||||
* @param string|null $transcription Audio transcription.
|
||||
*/
|
||||
public function __construct(
|
||||
public MediaAttachmentPayload $payload,
|
||||
public ?string $transcription,
|
||||
) {
|
||||
parent::__construct(AttachmentType::Audio);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
abstract readonly class AbstractButton extends AbstractModel
|
||||
{
|
||||
public function __construct(
|
||||
public ButtonType $type,
|
||||
public string $text,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
abstract readonly class AbstractInlineButton extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param InlineButtonType $type The type of the inline button.
|
||||
* @param string $text Visible text of the button.
|
||||
*/
|
||||
public function __construct(
|
||||
public InlineButtonType $type,
|
||||
public string $text,
|
||||
) {
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -2,15 +2,15 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons;
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\Intent;
|
||||
|
||||
/**
|
||||
* Sends a notification with payload to a bot (via WebHook or long polling).
|
||||
*/
|
||||
final readonly class CallbackButton extends AbstractButton
|
||||
final readonly class CallbackButton extends AbstractInlineButton
|
||||
{
|
||||
public string $payload;
|
||||
public ?Intent $intent;
|
||||
@@ -22,7 +22,7 @@ final readonly class CallbackButton extends AbstractButton
|
||||
*/
|
||||
public function __construct(string $text, string $payload, ?Intent $intent = null)
|
||||
{
|
||||
parent::__construct(ButtonType::Callback, $text);
|
||||
parent::__construct(InlineButtonType::Callback, $text);
|
||||
|
||||
$this->payload = $payload;
|
||||
$this->intent = $intent;
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
|
||||
/**
|
||||
* Button that creates a new chat associated with the message.
|
||||
* The bot will be added as an administrator by default.
|
||||
*/
|
||||
final readonly class ChatButton extends AbstractInlineButton
|
||||
{
|
||||
/**
|
||||
* @param string $text Visible text of the button (1 to 128 characters).
|
||||
* @param string $chatTitle Title of the chat to be created (max 200 characters).
|
||||
* @param string|null $chatDescription Optional chat description (max 400 characters).
|
||||
* @param string|null $startPayload Optional payload that will be sent to the bot in a `message_chat_created` update.
|
||||
* @param int|null $uuid Optional unique identifier for the button. If not passed, it will be generated.
|
||||
*/
|
||||
public function __construct(
|
||||
string $text,
|
||||
public string $chatTitle,
|
||||
public ?string $chatDescription = null,
|
||||
public ?string $startPayload = null,
|
||||
public ?int $uuid = null,
|
||||
) {
|
||||
parent::__construct(InlineButtonType::Chat, $text);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -2,14 +2,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons;
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
|
||||
/**
|
||||
* Makes a user to follow a link.
|
||||
*/
|
||||
final readonly class LinkButton extends AbstractButton
|
||||
final readonly class LinkButton extends AbstractInlineButton
|
||||
{
|
||||
public string $url;
|
||||
|
||||
@@ -19,7 +19,7 @@ final readonly class LinkButton extends AbstractButton
|
||||
*/
|
||||
public function __construct(string $text, string $url)
|
||||
{
|
||||
parent::__construct(ButtonType::Link, $text);
|
||||
parent::__construct(InlineButtonType::Link, $text);
|
||||
|
||||
$this->url = $url;
|
||||
}
|
||||
+4
-4
@@ -2,14 +2,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons;
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
|
||||
/**
|
||||
* Opens the bot's mini-application.
|
||||
*/
|
||||
final readonly class OpenAppButton extends AbstractButton
|
||||
final readonly class OpenAppButton extends AbstractInlineButton
|
||||
{
|
||||
public ?string $webApp;
|
||||
public ?int $contactId;
|
||||
@@ -21,7 +21,7 @@ final readonly class OpenAppButton extends AbstractButton
|
||||
*/
|
||||
public function __construct(string $text, ?string $webApp = null, ?int $contactId = null)
|
||||
{
|
||||
parent::__construct(ButtonType::OpenApp, $text);
|
||||
parent::__construct(InlineButtonType::OpenApp, $text);
|
||||
|
||||
$this->webApp = $webApp;
|
||||
$this->contactId = $contactId;
|
||||
+4
-4
@@ -2,20 +2,20 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons;
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
|
||||
/**
|
||||
* Requests the user permission to access contact information (phone number, short link, email).
|
||||
*/
|
||||
final readonly class RequestContactButton extends AbstractButton
|
||||
final readonly class RequestContactButton extends AbstractInlineButton
|
||||
{
|
||||
/**
|
||||
* @param string $text Visible button text (1 to 128 characters).
|
||||
*/
|
||||
public function __construct(string $text)
|
||||
{
|
||||
parent::__construct(ButtonType::RequestContact, $text);
|
||||
parent::__construct(InlineButtonType::RequestContact, $text);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -2,14 +2,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons;
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
|
||||
/**
|
||||
* After pressing this type of button client sends new message with attachment of current user geo location.
|
||||
*/
|
||||
final readonly class RequestGeoLocationButton extends AbstractButton
|
||||
final readonly class RequestGeoLocationButton extends AbstractInlineButton
|
||||
{
|
||||
public bool $quick;
|
||||
|
||||
@@ -19,7 +19,7 @@ final readonly class RequestGeoLocationButton extends AbstractButton
|
||||
*/
|
||||
public function __construct(string $text, bool $quick = false)
|
||||
{
|
||||
parent::__construct(ButtonType::RequestGeoLocation, $text);
|
||||
parent::__construct(InlineButtonType::RequestGeoLocation, $text);
|
||||
|
||||
$this->quick = $quick;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ReplyButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
abstract readonly class AbstractReplyButton extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param ReplyButtonType $type The type of the reply button.
|
||||
* @param string $text Visible text of the button.
|
||||
*/
|
||||
public function __construct(
|
||||
public ReplyButtonType $type,
|
||||
public string $text,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ReplyButtonType;
|
||||
|
||||
final readonly class SendContactButton extends AbstractReplyButton
|
||||
{
|
||||
/**
|
||||
* @param string $text Visible text of the button.
|
||||
*/
|
||||
public function __construct(string $text)
|
||||
{
|
||||
parent::__construct(ReplyButtonType::UserContact, $text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ReplyButtonType;
|
||||
|
||||
final readonly class SendGeoLocationButton extends AbstractReplyButton
|
||||
{
|
||||
/**
|
||||
* @param string $text Visible text of the button.
|
||||
* @param bool $quick If `true`, sends location without asking user's confirmation.
|
||||
*/
|
||||
public function __construct(
|
||||
string $text,
|
||||
public bool $quick = false,
|
||||
) {
|
||||
parent::__construct(ReplyButtonType::UserGeoLocation, $text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\Intent;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ReplyButtonType;
|
||||
|
||||
final readonly class SendMessageButton extends AbstractReplyButton
|
||||
{
|
||||
/**
|
||||
* @param string $text Visible text of the button.
|
||||
* @param string|null $payload Button payload.
|
||||
* @param Intent $intent Intent of button.
|
||||
*/
|
||||
public function __construct(
|
||||
string $text,
|
||||
public ?string $payload = null,
|
||||
public Intent $intent = Intent::Default,
|
||||
) {
|
||||
parent::__construct(ReplyButtonType::Message, $text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ContactAttachmentPayload;
|
||||
|
||||
final readonly class ContactAttachment extends AbstractAttachment
|
||||
{
|
||||
/**
|
||||
* @param ContactAttachmentPayload $payload Contact attachment payload.
|
||||
*/
|
||||
public function __construct(public ContactAttachmentPayload $payload)
|
||||
{
|
||||
parent::__construct(AttachmentType::Contact);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
|
||||
/**
|
||||
* Represents an attachment containing a payload from a SendMessageButton.
|
||||
*/
|
||||
final readonly class DataAttachment extends AbstractAttachment
|
||||
{
|
||||
/**
|
||||
* @param string $data The payload from the button.
|
||||
*/
|
||||
public function __construct(public string $data)
|
||||
{
|
||||
parent::__construct(AttachmentType::Data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\FileAttachmentPayload;
|
||||
|
||||
final readonly class FileAttachment extends AbstractAttachment
|
||||
{
|
||||
/**
|
||||
* @param FileAttachmentPayload $payload File attachment payload.
|
||||
* @param string $filename Uploaded file name.
|
||||
* @param int $size File size in bytes.
|
||||
*/
|
||||
public function __construct(
|
||||
public FileAttachmentPayload $payload,
|
||||
public string $filename,
|
||||
public int $size,
|
||||
) {
|
||||
parent::__construct(AttachmentType::File);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\KeyboardPayload;
|
||||
|
||||
final readonly class InlineKeyboardAttachment extends AbstractAttachment
|
||||
{
|
||||
/**
|
||||
* @param KeyboardPayload $payload Keyboard payload.
|
||||
*/
|
||||
public function __construct(public KeyboardPayload $payload)
|
||||
{
|
||||
parent::__construct(AttachmentType::InlineKeyboard);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
|
||||
final readonly class LocationAttachment extends AbstractAttachment
|
||||
{
|
||||
/**
|
||||
* @param float $latitude
|
||||
* @param float $longitude
|
||||
*/
|
||||
public function __construct(
|
||||
public float $latitude,
|
||||
public float $longitude,
|
||||
) {
|
||||
parent::__construct(AttachmentType::Location);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
|
||||
/**
|
||||
* Payload of a contact attachment.
|
||||
*/
|
||||
final readonly class ContactAttachmentPayload extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string|null $vcfInfo User info in VCF format.
|
||||
* @param User|null $maxInfo User info if the contact is a Max user.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $vcfInfo,
|
||||
public ?User $maxInfo,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
/**
|
||||
* Payload for a file attachment.
|
||||
*/
|
||||
final readonly class FileAttachmentPayload extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string $url Media attachment URL.
|
||||
* @param string $token Token to reuse the same attachment in other messages.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public string $token,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\AbstractButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\AbstractInlineButton;
|
||||
|
||||
final readonly class InlineKeyboardAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param AbstractButton[][] $buttons
|
||||
* @param AbstractInlineButton[][] $buttons
|
||||
*/
|
||||
public function __construct(
|
||||
public array $buttons,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\AbstractInlineButton;
|
||||
|
||||
/**
|
||||
* Represents an inline keyboard structure.
|
||||
*/
|
||||
final readonly class KeyboardPayload extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param AbstractInlineButton[][] $buttons Two-dimensional array of buttons.
|
||||
*/
|
||||
public function __construct(public array $buttons)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
/**
|
||||
* Payload for media attachments like audio or video.
|
||||
*/
|
||||
final readonly class MediaAttachmentPayload extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string $url Media attachment URL.
|
||||
* @param string $token Token to reuse the same attachment in other messages.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public string $token,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
/**
|
||||
* Payload for a photo attachment.
|
||||
*/
|
||||
final readonly class PhotoAttachmentPayload extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string $url Media attachment URL.
|
||||
* @param string $token Token to reuse the same attachment in other messages.
|
||||
* @param int $photoId Unique identifier of this image.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public string $token,
|
||||
public int $photoId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,7 @@ final readonly class PhotoToken extends AbstractModel
|
||||
/**
|
||||
* @param string $token Encoded information of uploaded image.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $token,
|
||||
) {
|
||||
public function __construct(public string $token)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\AbstractReplyButton;
|
||||
|
||||
final readonly class ReplyKeyboardAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param AbstractReplyButton[][] $buttons Two-dimensional array of buttons.
|
||||
* @param bool $direct Applicable only for chats.
|
||||
* @param int|null $directUserId If set, reply keyboard will only be shown to this participant.
|
||||
*/
|
||||
public function __construct(
|
||||
public array $buttons,
|
||||
public bool $direct = false,
|
||||
public ?int $directUserId = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
/**
|
||||
* Payload for a sticker attachment.
|
||||
*/
|
||||
final readonly class StickerAttachmentPayload extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string $url Media attachment URL.
|
||||
* @param string $code Sticker identifier.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public string $code,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
/**
|
||||
* Payload for attachments that are uploaded to the server first (video, audio, file).
|
||||
*/
|
||||
final readonly class UploadedInfoAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param string $token The unique token received after a successful file upload.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $token,
|
||||
) {
|
||||
}
|
||||
}
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
/**
|
||||
* Payload for attachments that are uploaded to the server first (video, audio, file).
|
||||
*/
|
||||
final readonly class UploadedInfoAttachmentRequestPayload extends AbstractAttachmentRequestPayload
|
||||
{
|
||||
/**
|
||||
* @param string $token The unique token received after a successful file upload.
|
||||
*/
|
||||
public function __construct(public string $token)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
/**
|
||||
* Represents a video thumbnail image.
|
||||
*/
|
||||
final readonly class VideoThumbnail extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string $url Media attachment URL.
|
||||
*/
|
||||
public function __construct(public string $url)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentPayload;
|
||||
|
||||
final readonly class PhotoAttachment extends AbstractAttachment
|
||||
{
|
||||
/**
|
||||
* @param PhotoAttachmentPayload $payload Photo attachment payload.
|
||||
*/
|
||||
public function __construct(public PhotoAttachmentPayload $payload)
|
||||
{
|
||||
parent::__construct(AttachmentType::Image);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\AbstractReplyButton;
|
||||
|
||||
final readonly class ReplyKeyboardAttachment extends AbstractAttachment
|
||||
{
|
||||
/**
|
||||
* @param AbstractReplyButton[][] $buttons
|
||||
*/
|
||||
public function __construct(public array $buttons)
|
||||
{
|
||||
parent::__construct(AttachmentType::ReplyKeyboard);
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,13 @@ declare(strict_types=1);
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\AbstractButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\AbstractInlineButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardAttachmentRequestPayload;
|
||||
|
||||
final readonly class InlineKeyboardAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* @param AbstractButton[][] $buttons
|
||||
* @param AbstractInlineButton[][] $buttons
|
||||
*/
|
||||
public function __construct(array $buttons)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\AbstractReplyButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ReplyKeyboardAttachmentRequestPayload;
|
||||
|
||||
final readonly class ReplyKeyboardAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* @param AbstractReplyButton[][] $buttons
|
||||
* @param bool $direct
|
||||
* @param int|null $directUserId
|
||||
*/
|
||||
public function __construct(
|
||||
array $buttons,
|
||||
bool $direct = false,
|
||||
?int $directUserId = null
|
||||
) {
|
||||
parent::__construct(
|
||||
AttachmentType::ReplyKeyboard,
|
||||
new ReplyKeyboardAttachmentRequestPayload($buttons, $direct, $directUserId),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ShareAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Represents a share (URL preview) attachment.
|
||||
*/
|
||||
final readonly class ShareAttachment extends AbstractAttachment
|
||||
{
|
||||
public function __construct(
|
||||
public ShareAttachmentRequestPayload $payload,
|
||||
public ?string $title,
|
||||
public ?string $description,
|
||||
public ?string $imageUrl,
|
||||
) {
|
||||
parent::__construct(AttachmentType::Share);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\StickerAttachmentPayload;
|
||||
|
||||
final readonly class StickerAttachment extends AbstractAttachment
|
||||
{
|
||||
/**
|
||||
* @param StickerAttachmentPayload $payload Sticker attachment payload.
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
*/
|
||||
public function __construct(
|
||||
public StickerAttachmentPayload $payload,
|
||||
public int $width,
|
||||
public int $height,
|
||||
) {
|
||||
parent::__construct(AttachmentType::Sticker);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\MediaAttachmentPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\VideoThumbnail;
|
||||
|
||||
final readonly class VideoAttachment extends AbstractAttachment
|
||||
{
|
||||
/**
|
||||
* @param MediaAttachmentPayload $payload Video attachment payload.
|
||||
* @param VideoThumbnail|null $thumbnail Video thumbnail.
|
||||
* @param int|null $width Video width.
|
||||
* @param int|null $height Video height.
|
||||
* @param int|null $duration Video duration in seconds.
|
||||
*/
|
||||
public function __construct(
|
||||
public MediaAttachmentPayload $payload,
|
||||
public ?VideoThumbnail $thumbnail,
|
||||
public ?int $width,
|
||||
public ?int $height,
|
||||
public ?int $duration,
|
||||
) {
|
||||
parent::__construct(AttachmentType::Video);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Represents the data to patch for a bot. Instantiate with named arguments.
|
||||
*
|
||||
* Example: new BotPatch(name: 'New Bot Name', description: null);
|
||||
*
|
||||
* @property-read string|null $name
|
||||
* @property-read string|null $description
|
||||
* @property-read BotCommand[]|null $commands
|
||||
* @property-read PhotoAttachmentRequestPayload|null $photo
|
||||
*/
|
||||
final readonly class BotPatch extends AbstractPatchModel
|
||||
{
|
||||
}
|
||||
+2
-2
@@ -24,7 +24,7 @@ final readonly class Chat extends AbstractModel
|
||||
* @param int|null $ownerId Identifier of chat owner. Visible only for chat admins
|
||||
* @param string|null $link Link on chat.
|
||||
* @param string|null $description Chat description.
|
||||
* @param User|null $dialogWithUser Another user in conversation. For `dialog` type chats only.
|
||||
* @param UserWithPhoto|null $dialogWithUser Another user in conversation. For `dialog` type chats only.
|
||||
* @param int|null $messagesCount Messages count in chat. Only for group chats and channels. Not available for dialogs.
|
||||
* @param string|null $chatMessageId Identifier of message that contains `chat` button initialized chat.
|
||||
* @param Message|null $pinnedMessage Pinned message in chat or channel. Returned only when single chat is requested.
|
||||
@@ -41,7 +41,7 @@ final readonly class Chat extends AbstractModel
|
||||
public ?int $ownerId,
|
||||
public ?string $link,
|
||||
public ?string $description,
|
||||
public ?User $dialogWithUser,
|
||||
public ?UserWithPhoto $dialogWithUser,
|
||||
public ?int $messagesCount,
|
||||
public ?string $chatMessageId,
|
||||
public ?Message $pinnedMessage,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ChatAdminPermission;
|
||||
|
||||
/**
|
||||
* Represents an administrator to be set in a chat, linking a user ID with their permissions.
|
||||
*/
|
||||
final readonly class ChatAdmin extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param int $userId The identifier of the user to be made an admin.
|
||||
* @param ChatAdminPermission[] $permissions The list of permissions to grant to the user.
|
||||
*/
|
||||
public function __construct(
|
||||
public int $userId,
|
||||
#[ArrayOf(ChatAdminPermission::class)]
|
||||
public array $permissions,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Represents the data to patch for a chat. Instantiate with named arguments.
|
||||
*
|
||||
* Example: new ChatPatch(title: 'New Chat Title');
|
||||
*
|
||||
* @property-read PhotoAttachmentRequestPayload|null $icon
|
||||
* @property-read string|null $title
|
||||
* @property-read string|null $pin Message ID to be pinned.
|
||||
* @property-read bool|null $notify
|
||||
*/
|
||||
final readonly class ChatPatch extends AbstractPatchModel
|
||||
{
|
||||
}
|
||||
@@ -9,8 +9,7 @@ final readonly class Image extends AbstractModel
|
||||
/**
|
||||
* @param string $url URL of image.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $url,
|
||||
) {
|
||||
public function __construct(public string $url)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MessageLinkType;
|
||||
|
||||
/**
|
||||
* Represents a forwarded or replied message linked to the main message.
|
||||
*/
|
||||
final readonly class LinkedMessage extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param MessageLinkType $type Type of linked message (forward or reply).
|
||||
* @param MessageBody $message The body of the original message.
|
||||
* @param User|null $sender The sender of the original message. Can be null if posted on behalf of a channel.
|
||||
* @param int|null $chatId The chat where the message was originally posted (for forwarded messages).
|
||||
*/
|
||||
public function __construct(
|
||||
public MessageLinkType $type,
|
||||
public MessageBody $message,
|
||||
public ?User $sender,
|
||||
public ?int $chatId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Markup;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
/**
|
||||
* Base class for a text markup element.
|
||||
*/
|
||||
abstract readonly class AbstractMarkup extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param MarkupType $type The type of the markup element.
|
||||
* @param int $from Element start index (zero-based) in text.
|
||||
* @param int $length Length of the markup element.
|
||||
*/
|
||||
public function __construct(
|
||||
public MarkupType $type,
|
||||
public int $from,
|
||||
public int $length,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Markup;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
|
||||
/**
|
||||
* Represents an *emphasized* (italic) part of the text.
|
||||
*/
|
||||
final readonly class EmphasizedMarkup extends AbstractMarkup
|
||||
{
|
||||
/**
|
||||
* @param int $from Element start index (zero-based) in text.
|
||||
* @param int $length Length of the markup element.
|
||||
*/
|
||||
public function __construct(
|
||||
int $from,
|
||||
int $length,
|
||||
) {
|
||||
parent::__construct(MarkupType::Emphasized, $from, $length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Markup;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
|
||||
/**
|
||||
* Represents a # header part of the text.
|
||||
*/
|
||||
final readonly class HeadingMarkup extends AbstractMarkup
|
||||
{
|
||||
/**
|
||||
* @param int $from Element start index (zero-based) in text.
|
||||
* @param int $length Length of the markup element.
|
||||
*/
|
||||
public function __construct(
|
||||
int $from,
|
||||
int $length,
|
||||
) {
|
||||
parent::__construct(MarkupType::Heading, $from, $length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Markup;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
|
||||
/**
|
||||
* Represents a ^^highlighted^^ part of the text.
|
||||
*/
|
||||
final readonly class HighlightedMarkup extends AbstractMarkup
|
||||
{
|
||||
/**
|
||||
* @param int $from Element start index (zero-based) in text.
|
||||
* @param int $length Length of the markup element.
|
||||
*/
|
||||
public function __construct(
|
||||
int $from,
|
||||
int $length,
|
||||
) {
|
||||
parent::__construct(MarkupType::Highlighted, $from, $length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Markup;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
|
||||
/**
|
||||
* Represents a link in the text.
|
||||
*/
|
||||
final readonly class LinkMarkup extends AbstractMarkup
|
||||
{
|
||||
/**
|
||||
* @param int $from Element start index (zero-based) in text.
|
||||
* @param int $length Length of the markup element.
|
||||
* @param string $url Link's URL.
|
||||
*/
|
||||
public function __construct(
|
||||
int $from,
|
||||
int $length,
|
||||
public string $url,
|
||||
) {
|
||||
parent::__construct(MarkupType::Link, $from, $length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Markup;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
|
||||
/**
|
||||
* Represents a `monospaced` part of the text.
|
||||
*/
|
||||
final readonly class MonospacedMarkup extends AbstractMarkup
|
||||
{
|
||||
/**
|
||||
* @param int $from Element start index (zero-based) in text.
|
||||
* @param int $length Length of the markup element.
|
||||
*/
|
||||
public function __construct(
|
||||
int $from,
|
||||
int $length,
|
||||
) {
|
||||
parent::__construct(MarkupType::Monospaced, $from, $length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Markup;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
|
||||
/**
|
||||
* Represents a ~strikethrough~ part of the text.
|
||||
*/
|
||||
final readonly class StrikethroughMarkup extends AbstractMarkup
|
||||
{
|
||||
/**
|
||||
* @param int $from Element start index (zero-based) in text.
|
||||
* @param int $length Length of the markup element.
|
||||
*/
|
||||
public function __construct(
|
||||
int $from,
|
||||
int $length,
|
||||
) {
|
||||
parent::__construct(MarkupType::Strikethrough, $from, $length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Markup;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
|
||||
/**
|
||||
* Represents a **strong** (bold) part of the text.
|
||||
*/
|
||||
final readonly class StrongMarkup extends AbstractMarkup
|
||||
{
|
||||
/**
|
||||
* @param int $from Element start index (zero-based) in text.
|
||||
* @param int $length Length of the markup element.
|
||||
*/
|
||||
public function __construct(
|
||||
int $from,
|
||||
int $length,
|
||||
) {
|
||||
parent::__construct(MarkupType::Strong, $from, $length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Markup;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
|
||||
/**
|
||||
* Represents an ++underlined++ part of the text.
|
||||
*/
|
||||
final readonly class UnderlineMarkup extends AbstractMarkup
|
||||
{
|
||||
/**
|
||||
* @param int $from Element start index (zero-based) in text.
|
||||
* @param int $length Length of the markup element.
|
||||
*/
|
||||
public function __construct(
|
||||
int $from,
|
||||
int $length,
|
||||
) {
|
||||
parent::__construct(MarkupType::Underline, $from, $length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Markup;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
|
||||
|
||||
/**
|
||||
* Represents a user mention in the text.
|
||||
*/
|
||||
final readonly class UserMentionMarkup extends AbstractMarkup
|
||||
{
|
||||
/**
|
||||
* @param int $from Element start index (zero-based) in text.
|
||||
* @param int $length Length of the markup element.
|
||||
* @param string|null $userLink "@username" of the mentioned user.
|
||||
* @param int|null $userId Identifier of the mentioned user without a username.
|
||||
*/
|
||||
public function __construct(
|
||||
int $from,
|
||||
int $length,
|
||||
public ?string $userLink,
|
||||
public ?int $userId,
|
||||
) {
|
||||
parent::__construct(MarkupType::UserMention, $from, $length);
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,21 @@ final readonly class Message extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param int $timestamp Unix-time when message was created.
|
||||
* @param MessageBody $body Body of created message. Text + attachments.
|
||||
* @param Recipient $recipient Message recipient. Could be user or chat.
|
||||
* @param Sender|null $sender User who sent this message. Can be null if message has been posted on behalf of a channel.
|
||||
* @param MessageBody|null $body Body of created message. Text + attachments.
|
||||
* @param User|null $sender User who sent this message. Can be null if message has been posted on behalf of a channel.
|
||||
* @param string|null $url Message public URL. Can be null for dialogs or non-public chats/channels.
|
||||
* @param LinkedMessage|null $link Forwarded or replied message.
|
||||
* @param MessageStat|null $stat Message statistics. Available only for channels.
|
||||
*/
|
||||
public function __construct(
|
||||
public int $timestamp,
|
||||
public MessageBody $body,
|
||||
public Recipient $recipient,
|
||||
public ?Sender $sender,
|
||||
public ?MessageBody $body,
|
||||
public ?User $sender,
|
||||
public ?string $url,
|
||||
public ?LinkedMessage $link,
|
||||
public ?MessageStat $stat,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\AbstractAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\AbstractMarkup;
|
||||
|
||||
/**
|
||||
* Body of created message. Text + attachments.
|
||||
*/
|
||||
@@ -13,11 +16,15 @@ final readonly class MessageBody extends AbstractModel
|
||||
* @param string $mid Unique identifier of message.
|
||||
* @param int $seq Sequence identifier of message in chat.
|
||||
* @param string|null $text Message text.
|
||||
* @param AbstractAttachment[]|null $attachments Message attachments.
|
||||
* @param AbstractMarkup[]|null $markup Message text markup.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $mid,
|
||||
public int $seq,
|
||||
public ?string $text,
|
||||
public ?array $attachments,
|
||||
public ?array $markup,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
/**
|
||||
* Message statistics.
|
||||
*/
|
||||
final readonly class MessageStat extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param int $views Number of views.
|
||||
*/
|
||||
public function __construct(public int $views)
|
||||
{
|
||||
}
|
||||
}
|
||||
+2
-7
@@ -11,11 +11,9 @@ final readonly class User extends AbstractModel
|
||||
* @param string $firstName Users first name.
|
||||
* @param string|null $lastName Users last name.
|
||||
* @param string|null $username Unique public user name. Can be `null` if user is not accessible or it is not set.
|
||||
* @param bool $isBot `true` if user is bot.
|
||||
* @param bool $isBot Is the user a bot.
|
||||
* @param int $lastActivityTime Time of last user activity in Max (Unix timestamp in milliseconds).
|
||||
* @param string|null $description User description. Can be `null` if user did not fill it out.
|
||||
* @param string|null $avatarUrl URL of avatar.
|
||||
* @param string|null $fullAvatarUrl URL of avatar of a bigger size.
|
||||
* Can be outdated if user disabled its "online" status in settings.
|
||||
*/
|
||||
public function __construct(
|
||||
public int $userId,
|
||||
@@ -24,9 +22,6 @@ final readonly class User extends AbstractModel
|
||||
public ?string $username,
|
||||
public bool $isBot,
|
||||
public int $lastActivityTime,
|
||||
public ?string $description,
|
||||
public ?string $avatarUrl,
|
||||
public ?string $fullAvatarUrl,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
/**
|
||||
* User who sent this message.
|
||||
*/
|
||||
final readonly class Sender extends AbstractModel
|
||||
final readonly class UserWithPhoto extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param int $userId Users identifier.
|
||||
* @param string $firstName Users first name.
|
||||
* @param string|null $lastName Users last name.
|
||||
* @param string|null $username Unique public user name. Can be null if user is not accessible or it is not set.
|
||||
* @param bool $isBot Is the user a bot.
|
||||
* @param int $lastActivityTime Time of last user activity in Max (Unix timestamp in milliseconds). Can be outdated if user disabled its "online" status in settings.
|
||||
* @param string|null $username Unique public user name. Can be `null` if user is not accessible or it is not set.
|
||||
* @param bool $isBot `true` if user is bot.
|
||||
* @param int $lastActivityTime Time of last user activity in Max (Unix timestamp in milliseconds).
|
||||
* @param string|null $description UserWithPhoto description. Can be `null` if user did not fill it out.
|
||||
* @param string|null $avatarUrl URL of avatar.
|
||||
* @param string|null $fullAvatarUrl URL of avatar of a bigger size.
|
||||
*/
|
||||
public function __construct(
|
||||
public int $userId,
|
||||
@@ -24,6 +24,9 @@ final readonly class Sender extends AbstractModel
|
||||
public ?string $username,
|
||||
public bool $isBot,
|
||||
public int $lastActivityTime,
|
||||
public ?string $description,
|
||||
public ?string $avatarUrl,
|
||||
public ?string $fullAvatarUrl,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentRequestPayload;
|
||||
|
||||
/**
|
||||
* Contains detailed information about a video attachment.
|
||||
*/
|
||||
final readonly class VideoAttachmentDetails extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string $token The video attachment token.
|
||||
* @param int $width The width of the video in pixels.
|
||||
* @param int $height The height of the video in pixels.
|
||||
* @param int $duration The duration of the video in seconds.
|
||||
* @param VideoUrls|null $urls URLs to download or play the video. Can be null if the video is unavailable.
|
||||
* @param PhotoAttachmentRequestPayload|null $thumbnail The video's thumbnail image information.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $token,
|
||||
public int $width,
|
||||
public int $height,
|
||||
public int $duration,
|
||||
public ?VideoUrls $urls = null,
|
||||
public ?PhotoAttachmentRequestPayload $thumbnail = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
/**
|
||||
* Contains URLs for a video attachment in various resolutions.
|
||||
*/
|
||||
final readonly class VideoUrls extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string|null $mp4_1080 Video URL in 1080p resolution, if available.
|
||||
* @param string|null $mp4_720 Video URL in 720p resolution, if available.
|
||||
* @param string|null $mp4_480 Video URL in 480p resolution, if available.
|
||||
* @param string|null $mp4_360 Video URL in 360p resolution, if available.
|
||||
* @param string|null $mp4_240 Video URL in 240p resolution, if available.
|
||||
* @param string|null $mp4_144 Video URL in 144p resolution, if available.
|
||||
* @param string|null $hls Live streaming URL (HLS), if available.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $mp4_1080 = null,
|
||||
public ?string $mp4_720 = null,
|
||||
public ?string $mp4_480 = null,
|
||||
public ?string $mp4_360 = null,
|
||||
public ?string $mp4_240 = null,
|
||||
public ?string $mp4_144 = null,
|
||||
public ?string $hls = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ use BushlanovDev\MaxMessengerBot\Exceptions\SecurityException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\SerializationException;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* A class designed to process incoming webhook requests from the Max API.
|
||||
@@ -26,11 +28,13 @@ final class WebhookHandler
|
||||
* @param Api $api An instance of the Api to be passed to handlers for immediate responses.
|
||||
* @param ModelFactory $modelFactory An instance of the model factory to create Update objects.
|
||||
* @param string|null $secret The secret key provided during webhook subscription to verify requests.
|
||||
* @param LoggerInterface $logger A PSR-3 compatible logger.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Api $api,
|
||||
private readonly ModelFactory $modelFactory,
|
||||
private readonly ?string $secret = null,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -254,6 +258,8 @@ final class WebhookHandler
|
||||
$payload = (string)$request->getBody();
|
||||
$signature = $request->getHeaderLine('X-Max-Bot-Api-Secret');
|
||||
|
||||
$this->logger->debug('Received webhook payload', ['body' => $payload]);
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new SerializationException('Webhook body is empty.');
|
||||
}
|
||||
@@ -263,6 +269,7 @@ final class WebhookHandler
|
||||
try {
|
||||
$data = json_decode($payload, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (\JsonException $e) {
|
||||
$this->logger->error('Failed to decode webhook JSON', ['payload' => $payload, 'exception' => $e]);
|
||||
throw new SerializationException('Failed to decode webhook body as JSON.', 0, $e);
|
||||
}
|
||||
|
||||
@@ -297,6 +304,7 @@ final class WebhookHandler
|
||||
}
|
||||
|
||||
if (!hash_equals($this->secret, $signature)) {
|
||||
$this->logger->warning('Webhook signature verification failed', ['received_signature' => $signature]);
|
||||
throw new SecurityException('Signature verification failed.');
|
||||
}
|
||||
}
|
||||
|
||||
+355
-17
@@ -9,14 +9,15 @@ use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf;
|
||||
use BushlanovDev\MaxMessengerBot\Client;
|
||||
use BushlanovDev\MaxMessengerBot\ClientApiInterface;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ChatAdminPermission;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
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;
|
||||
use BushlanovDev\MaxMessengerBot\ModelFactory;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ContactAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\LocationAttachmentRequestPayload;
|
||||
@@ -35,22 +36,26 @@ use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\ShareAttachmentRequ
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\StickerAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\VideoAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotPatch;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Chat;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatAdmin;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatMember;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatMembersList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatPatch;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\MessageBody;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Recipient;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Sender;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
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\Models\VideoAttachmentDetails;
|
||||
use BushlanovDev\MaxMessengerBot\Models\VideoUrls;
|
||||
use BushlanovDev\MaxMessengerBot\WebhookHandler;
|
||||
use GuzzleHttp\Psr7\ServerRequest;
|
||||
use InvalidArgumentException;
|
||||
@@ -65,6 +70,7 @@ use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\MockObject\Exception;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ReflectionClass;
|
||||
use RuntimeException;
|
||||
|
||||
@@ -76,7 +82,7 @@ use RuntimeException;
|
||||
#[UsesClass(Message::class)]
|
||||
#[UsesClass(MessageBody::class)]
|
||||
#[UsesClass(Recipient::class)]
|
||||
#[UsesClass(Sender::class)]
|
||||
#[UsesClass(User::class)]
|
||||
#[UsesClass(CallbackButton::class)]
|
||||
#[UsesClass(InlineKeyboardAttachmentRequestPayload::class)]
|
||||
#[UsesClass(InlineKeyboardAttachmentRequest::class)]
|
||||
@@ -87,7 +93,6 @@ use RuntimeException;
|
||||
#[UsesClass(Chat::class)]
|
||||
#[UsesClass(UpdateList::class)]
|
||||
#[UsesClass(WebhookHandler::class)]
|
||||
#[UsesClass(User::class)]
|
||||
#[UsesClass(AbstractUpdate::class)]
|
||||
#[UsesClass(BotStartedUpdate::class)]
|
||||
#[UsesClass(MessageCreatedUpdate::class)]
|
||||
@@ -107,12 +112,19 @@ use RuntimeException;
|
||||
#[UsesClass(ChatMember::class)]
|
||||
#[UsesClass(ArrayOf::class)]
|
||||
#[UsesClass(ChatMembersList::class)]
|
||||
#[UsesClass(ChatAdmin::class)]
|
||||
#[UsesClass(BotPatch::class)]
|
||||
#[UsesClass(ChatPatch::class)]
|
||||
#[UsesClass(VideoAttachmentDetails::class)]
|
||||
#[UsesClass(VideoUrls::class)]
|
||||
final class ApiTest extends TestCase
|
||||
{
|
||||
use PHPMock;
|
||||
|
||||
private MockObject&ClientApiInterface $clientMock;
|
||||
private MockObject&ModelFactory $modelFactoryMock;
|
||||
private MockObject&LoggerInterface $loggerMock;
|
||||
|
||||
private Api $api;
|
||||
|
||||
/**
|
||||
@@ -124,8 +136,9 @@ final class ApiTest extends TestCase
|
||||
|
||||
$this->clientMock = $this->createMock(ClientApiInterface::class);
|
||||
$this->modelFactoryMock = $this->createMock(ModelFactory::class);
|
||||
$this->loggerMock = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$this->api = new Api('fake-token', $this->clientMock, $this->modelFactoryMock);
|
||||
$this->api = new Api('fake-token', $this->clientMock, $this->modelFactoryMock, $this->loggerMock);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -380,7 +393,7 @@ final class ApiTest extends TestCase
|
||||
'buttons' => [
|
||||
[
|
||||
[
|
||||
'type' => ButtonType::Callback->value,
|
||||
'type' => InlineButtonType::Callback->value,
|
||||
'text' => 'Press Me',
|
||||
'payload' => 'payload_123',
|
||||
'intent' => null,
|
||||
@@ -764,10 +777,12 @@ final class ApiTest extends TestCase
|
||||
$handlers = [UpdateType::MessageCreated->value => fn() => null];
|
||||
|
||||
$apiMock = $this->getMockBuilder(Api::class)
|
||||
->setConstructorArgs(['fake-token', $this->clientMock, $this->modelFactoryMock])
|
||||
->setConstructorArgs(['fake-token', $this->clientMock, $this->modelFactoryMock, $this->loggerMock])
|
||||
->onlyMethods(['processUpdatesBatch'])
|
||||
->getMock();
|
||||
|
||||
$this->loggerMock->expects($this->once())->method('error');
|
||||
|
||||
$apiMock->expects($this->any())
|
||||
->method('processUpdatesBatch')
|
||||
->willReturnCallback(function () {
|
||||
@@ -781,8 +796,6 @@ final class ApiTest extends TestCase
|
||||
}
|
||||
});
|
||||
|
||||
$this->expectOutputRegex('/Network error: Simulated network error/');
|
||||
|
||||
try {
|
||||
$apiMock->handleUpdates($handlers);
|
||||
} catch (\Error $e) {
|
||||
@@ -819,10 +832,12 @@ final class ApiTest extends TestCase
|
||||
$handlers = [UpdateType::MessageCreated->value => fn() => null];
|
||||
|
||||
$apiMock = $this->getMockBuilder(Api::class)
|
||||
->setConstructorArgs(['fake-token', $this->clientMock, $this->modelFactoryMock])
|
||||
->setConstructorArgs(['fake-token', $this->clientMock, $this->modelFactoryMock, $this->loggerMock])
|
||||
->onlyMethods(['processUpdatesBatch'])
|
||||
->getMock();
|
||||
|
||||
$this->loggerMock->expects($this->once())->method('error');
|
||||
|
||||
$apiMock->expects($this->any())
|
||||
->method('processUpdatesBatch')
|
||||
->willReturnCallback(function () {
|
||||
@@ -838,8 +853,6 @@ final class ApiTest extends TestCase
|
||||
}
|
||||
});
|
||||
|
||||
$this->expectOutputRegex('/An error occurred: Simulated JSON error/');
|
||||
|
||||
try {
|
||||
$apiMock->handleUpdates($handlers);
|
||||
} catch (\Error $e) {
|
||||
@@ -1827,7 +1840,7 @@ final class ApiTest extends TestCase
|
||||
$chatId = 12345;
|
||||
$userId = 678;
|
||||
$uri = sprintf('/chats/%d/members', $chatId);
|
||||
$expectedQuery = ['user_id' => $userId];
|
||||
$expectedQuery = ['user_id' => $userId, 'block' => false];
|
||||
|
||||
$rawResponse = ['success' => true];
|
||||
$expectedResult = new Result(true, null);
|
||||
@@ -1844,7 +1857,7 @@ final class ApiTest extends TestCase
|
||||
->with($rawResponse)
|
||||
->willReturn($expectedResult);
|
||||
|
||||
$result = $this->api->removeMember($chatId, $userId);
|
||||
$result = $this->api->deleteMember($chatId, $userId);
|
||||
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
@@ -1872,8 +1885,333 @@ final class ApiTest extends TestCase
|
||||
->with($rawResponse)
|
||||
->willReturn($expectedResult);
|
||||
|
||||
$result = $this->api->removeMember($chatId, $userId, true);
|
||||
$result = $this->api->deleteMember($chatId, $userId, true);
|
||||
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function postAdminsCallsClientWithCorrectBody(): void
|
||||
{
|
||||
$chatId = 12345;
|
||||
$uri = sprintf('/chats/%d/members/admins', $chatId);
|
||||
$admins = [
|
||||
new ChatAdmin(101, [ChatAdminPermission::Write]),
|
||||
new ChatAdmin(202, [ChatAdminPermission::PinMessage]),
|
||||
];
|
||||
|
||||
$expectedBody = [
|
||||
'admins' => [
|
||||
['user_id' => 101, 'permissions' => ['write']],
|
||||
['user_id' => 202, 'permissions' => ['pin_message']],
|
||||
],
|
||||
];
|
||||
$rawResponse = ['success' => true];
|
||||
$expectedResult = new Result(true, null);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', $uri, [], $expectedBody)
|
||||
->willReturn($rawResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createResult')
|
||||
->with($rawResponse)
|
||||
->willReturn($expectedResult);
|
||||
|
||||
$result = $this->api->addAdmins($chatId, $admins);
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function addMembersCallsClientWithCorrectBody(): void
|
||||
{
|
||||
$chatId = 12345;
|
||||
$userIds = [101, 202, 303];
|
||||
$uri = sprintf('/chats/%d/members', $chatId);
|
||||
$expectedBody = ['user_ids' => $userIds];
|
||||
|
||||
$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->addMembers($chatId, $userIds);
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function answerOnCallbackWithNotificationOnly(): void
|
||||
{
|
||||
$callbackId = 'cb.123.abc';
|
||||
$notification = 'Action confirmed!';
|
||||
$expectedQuery = ['callback_id' => $callbackId];
|
||||
$expectedBody = ['notification' => $notification];
|
||||
$rawResponse = ['success' => true];
|
||||
$expectedResult = new Result(true, null);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/answers', $expectedQuery, $expectedBody)
|
||||
->willReturn($rawResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createResult')
|
||||
->with($rawResponse)
|
||||
->willReturn($expectedResult);
|
||||
|
||||
$result = $this->api->answerOnCallback($callbackId, $notification);
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function answerOnCallbackWithMessageEdit(): void
|
||||
{
|
||||
$callbackId = 'cb.456.def';
|
||||
$newText = 'Message updated!';
|
||||
$expectedQuery = ['callback_id' => $callbackId];
|
||||
$expectedBody = [
|
||||
'message' => [
|
||||
'text' => $newText,
|
||||
'notify' => true,
|
||||
],
|
||||
];
|
||||
$rawResponse = ['success' => true];
|
||||
$expectedResult = new Result(true, null);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/answers', $expectedQuery, $expectedBody)
|
||||
->willReturn($rawResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createResult')
|
||||
->with($rawResponse)
|
||||
->willReturn($expectedResult);
|
||||
|
||||
$result = $this->api->answerOnCallback(callbackId: $callbackId, text: $newText);
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function answerOnCallbackWithBothMessageAndNotification(): void
|
||||
{
|
||||
$callbackId = 'cb.789.ghi';
|
||||
$notification = 'Done!';
|
||||
$newText = 'Updated!';
|
||||
$expectedQuery = ['callback_id' => $callbackId];
|
||||
$expectedBody = [
|
||||
'notification' => $notification,
|
||||
'message' => [
|
||||
'text' => $newText,
|
||||
'notify' => true,
|
||||
],
|
||||
];
|
||||
$rawResponse = ['success' => true];
|
||||
$expectedResult = new Result(true, null);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/answers', $expectedQuery, $expectedBody)
|
||||
->willReturn($rawResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createResult')
|
||||
->with($rawResponse)
|
||||
->willReturn($expectedResult);
|
||||
|
||||
$result = $this->api->answerOnCallback(
|
||||
callbackId: $callbackId,
|
||||
notification: $notification,
|
||||
text: $newText
|
||||
);
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function editMessageCallsClientWithCorrectParameters(): void
|
||||
{
|
||||
$messageId = 'mid.123.abc';
|
||||
$newText = 'This is the edited text.';
|
||||
$expectedQuery = ['message_id' => $messageId];
|
||||
$expectedBody = [
|
||||
'text' => $newText,
|
||||
'notify' => true,
|
||||
];
|
||||
|
||||
$rawResponse = ['success' => true];
|
||||
$expectedResult = new Result(true, null);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('PUT', '/messages', $expectedQuery, $expectedBody)
|
||||
->willReturn($rawResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createResult')
|
||||
->with($rawResponse)
|
||||
->willReturn($expectedResult);
|
||||
|
||||
$result = $this->api->editMessage($messageId, $newText);
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function editMessageCanClearAttachmentsWithEmptyArray(): void
|
||||
{
|
||||
$messageId = 'mid.456.def';
|
||||
$expectedQuery = ['message_id' => $messageId];
|
||||
$expectedBody = [
|
||||
'attachments' => [],
|
||||
'notify' => true,
|
||||
];
|
||||
|
||||
$rawResponse = ['success' => true];
|
||||
$expectedResult = new Result(true, null);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('PUT', '/messages', $expectedQuery, $expectedBody)
|
||||
->willReturn($rawResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createResult')
|
||||
->with($rawResponse)
|
||||
->willReturn($expectedResult);
|
||||
|
||||
$result = $this->api->editMessage($messageId, attachments: []);
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function editBotInfoSendsCorrectPatchBody(): void
|
||||
{
|
||||
$patch = new BotPatch(name: 'New Bot Name', description: null);
|
||||
|
||||
$expectedBody = [
|
||||
'name' => 'New Bot Name',
|
||||
'description' => null,
|
||||
];
|
||||
|
||||
$rawResponseData = [
|
||||
'user_id' => 123,
|
||||
'first_name' => 'New Bot Name',
|
||||
'is_bot' => true,
|
||||
'last_activity_time' => 1,
|
||||
'description' => null,
|
||||
];
|
||||
$expectedBotInfo = new BotInfo(123, 'New Bot Name', null, null, true, 1, null, null, null, null);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('PATCH', '/me', [], $expectedBody)
|
||||
->willReturn($rawResponseData);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createBotInfo')
|
||||
->with($rawResponseData)
|
||||
->willReturn($expectedBotInfo);
|
||||
|
||||
$this->api->editBotInfo($patch);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function editChatSendsCorrectPatchBody(): void
|
||||
{
|
||||
$chatId = 12345;
|
||||
$uri = sprintf('/chats/%d', $chatId);
|
||||
$patch = new ChatPatch(title: 'New Chat Title', notify: false);
|
||||
|
||||
$expectedBody = [
|
||||
'title' => 'New Chat Title',
|
||||
'notify' => false,
|
||||
];
|
||||
|
||||
$rawResponse = [
|
||||
'chat_id' => $chatId,
|
||||
'title' => 'New Chat Title',
|
||||
'type' => 'chat',
|
||||
'status' => 'active',
|
||||
'last_event_time' => 1,
|
||||
'participants_count' => 1,
|
||||
'is_public' => false,
|
||||
];
|
||||
$expectedChat = Chat::fromArray($rawResponse);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('PATCH', $uri, [], $expectedBody)
|
||||
->willReturn($rawResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createChat')
|
||||
->with($rawResponse)
|
||||
->willReturn($expectedChat);
|
||||
|
||||
$result = $this->api->editChat($chatId, $patch);
|
||||
$this->assertSame($expectedChat, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function getVideoAttachmentDetailsCallsClientCorrectly(): void
|
||||
{
|
||||
$videoToken = 'some_video_token_xyz';
|
||||
$uri = sprintf('/videos/%s', $videoToken);
|
||||
|
||||
$rawResponse = [
|
||||
'token' => $videoToken,
|
||||
'width' => 1920,
|
||||
'height' => 1080,
|
||||
'duration' => 120,
|
||||
'urls' => ['mp4_1080' => 'http://example.com/video.mp4'],
|
||||
];
|
||||
$expectedDetails = VideoAttachmentDetails::fromArray($rawResponse);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('GET', $uri)
|
||||
->willReturn($rawResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createVideoAttachmentDetails')
|
||||
->with($rawResponse)
|
||||
->willReturn($expectedDetails);
|
||||
|
||||
$result = $this->api->getVideoAttachmentDetails($videoToken);
|
||||
|
||||
$this->assertSame($expectedDetails, $result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamFactoryInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
#[CoversClass(Client::class)]
|
||||
final class ClientTest extends TestCase
|
||||
@@ -42,6 +43,7 @@ final class ClientTest extends TestCase
|
||||
private MockObject&RequestInterface $requestMock;
|
||||
private MockObject&ResponseInterface $responseMock;
|
||||
private MockObject&StreamInterface $streamMock;
|
||||
private MockObject&LoggerInterface $loggerMock;
|
||||
|
||||
private Client $client;
|
||||
|
||||
@@ -61,6 +63,7 @@ final class ClientTest extends TestCase
|
||||
$this->requestMock = $this->createMock(RequestInterface::class);
|
||||
$this->responseMock = $this->createMock(ResponseInterface::class);
|
||||
$this->streamMock = $this->createMock(StreamInterface::class);
|
||||
$this->loggerMock = $this->createMock(LoggerInterface::class);
|
||||
|
||||
// Common mock setups
|
||||
$this->requestFactoryMock->method('createRequest')->willReturn($this->requestMock);
|
||||
@@ -75,6 +78,7 @@ final class ClientTest extends TestCase
|
||||
$this->streamFactory,
|
||||
self::API_BASE_URL,
|
||||
self::API_VERSION,
|
||||
$this->loggerMock,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -376,4 +380,33 @@ final class ClientTest extends TestCase
|
||||
$this->streamMock->method('__toString')->willReturn('{not-a-valid-json');
|
||||
$this->client->upload('http://some.url', 'content', 'file.txt');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function requestLogsRequestAndResponseOnDebugLevel(): void
|
||||
{
|
||||
$this->responseMock->method('getStatusCode')->willReturn(200);
|
||||
$this->streamMock->method('__toString')->willReturn('{"success":true}');
|
||||
|
||||
$this->loggerMock
|
||||
->expects($this->exactly(2))
|
||||
->method('debug');
|
||||
|
||||
$this->client->request('GET', '/me');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleErrorResponseLogsWarning(): void
|
||||
{
|
||||
$this->responseMock->method('getStatusCode')->willReturn(404);
|
||||
$this->streamMock->method('__toString')->willReturn('{"code":"not.found","message":"Not Found"}');
|
||||
|
||||
$this->loggerMock
|
||||
->expects($this->once())
|
||||
->method('error')
|
||||
->with('API error response received', $this->anything());
|
||||
|
||||
$this->expectException(NotFoundException::class);
|
||||
|
||||
$this->client->request('GET', '/not/found');
|
||||
}
|
||||
}
|
||||
|
||||
+314
-6
@@ -8,6 +8,26 @@ use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ChatAdminPermission;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\ModelFactory;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\AbstractAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\AbstractInlineButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\ChatButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\LinkButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\RequestContactButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\RequestGeoLocationButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\AbstractReplyButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendContactButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendMessageButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\DataAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\InlineKeyboardAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\LocationAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\KeyboardPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ShareAttachmentRequestPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\PhotoAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\ReplyKeyboardAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\ShareAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotCommand;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Chat;
|
||||
@@ -15,11 +35,14 @@ use BushlanovDev\MaxMessengerBot\Models\ChatList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatMember;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatMembersList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Image;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\AbstractMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\LinkMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Markup\StrongMarkup;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\MessageBody;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Recipient;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Sender;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\BotStartedUpdate;
|
||||
@@ -27,8 +50,12 @@ use BushlanovDev\MaxMessengerBot\Models\Updates\ChatTitleChangedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageChatCreatedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCreatedUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use BushlanovDev\MaxMessengerBot\Models\User;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UserWithPhoto;
|
||||
use BushlanovDev\MaxMessengerBot\Models\VideoAttachmentDetails;
|
||||
use BushlanovDev\MaxMessengerBot\Models\VideoUrls;
|
||||
use LogicException;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -42,11 +69,11 @@ use PHPUnit\Framework\TestCase;
|
||||
#[UsesClass(Message::class)]
|
||||
#[UsesClass(MessageBody::class)]
|
||||
#[UsesClass(Recipient::class)]
|
||||
#[UsesClass(Sender::class)]
|
||||
#[UsesClass(User::class)]
|
||||
#[UsesClass(UpdateList::class)]
|
||||
#[UsesClass(BotStartedUpdate::class)]
|
||||
#[UsesClass(MessageCreatedUpdate::class)]
|
||||
#[UsesClass(User::class)]
|
||||
#[UsesClass(UserWithPhoto::class)]
|
||||
#[UsesClass(UploadEndpoint::class)]
|
||||
#[UsesClass(Chat::class)]
|
||||
#[UsesClass(Image::class)]
|
||||
@@ -55,6 +82,31 @@ use PHPUnit\Framework\TestCase;
|
||||
#[UsesClass(ChatList::class)]
|
||||
#[UsesClass(ChatMember::class)]
|
||||
#[UsesClass(ChatMembersList::class)]
|
||||
#[UsesClass(VideoAttachmentDetails::class)]
|
||||
#[UsesClass(PhotoAttachmentRequestPayload::class)]
|
||||
#[UsesClass(VideoUrls::class)]
|
||||
#[UsesClass(AbstractAttachment::class)]
|
||||
#[UsesClass(DataAttachment::class)]
|
||||
#[UsesClass(ShareAttachment::class)]
|
||||
#[UsesClass(ShareAttachmentRequestPayload::class)]
|
||||
#[UsesClass(LinkMarkup::class)]
|
||||
#[UsesClass(AbstractMarkup::class)]
|
||||
#[UsesClass(StrongMarkup::class)]
|
||||
#[UsesClass(PhotoAttachmentPayload::class)]
|
||||
#[UsesClass(PhotoAttachment::class)]
|
||||
#[UsesClass(AbstractReplyButton::class)]
|
||||
#[UsesClass(SendContactButton::class)]
|
||||
#[UsesClass(SendMessageButton::class)]
|
||||
#[UsesClass(ReplyKeyboardAttachment::class)]
|
||||
#[UsesClass(AbstractInlineButton::class)]
|
||||
#[UsesClass(ChatButton::class)]
|
||||
#[UsesClass(RequestContactButton::class)]
|
||||
#[UsesClass(CallbackButton::class)]
|
||||
#[UsesClass(RequestGeoLocationButton::class)]
|
||||
#[UsesClass(LinkButton::class)]
|
||||
#[UsesClass(LocationAttachment::class)]
|
||||
#[UsesClass(InlineKeyboardAttachment::class)]
|
||||
#[UsesClass(KeyboardPayload::class)]
|
||||
final class ModelFactoryTest extends TestCase
|
||||
{
|
||||
private ModelFactory $factory;
|
||||
@@ -199,7 +251,7 @@ final class ModelFactoryTest extends TestCase
|
||||
$this->assertInstanceOf(Message::class, $message);
|
||||
$this->assertInstanceOf(MessageBody::class, $message->body);
|
||||
$this->assertInstanceOf(Recipient::class, $message->recipient);
|
||||
$this->assertInstanceOf(Sender::class, $message->sender);
|
||||
$this->assertInstanceOf(User::class, $message->sender);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -249,7 +301,7 @@ final class ModelFactoryTest extends TestCase
|
||||
|
||||
$this->assertInstanceOf(Chat::class, $chat);
|
||||
$this->assertInstanceOf(Image::class, $chat->icon);
|
||||
$this->assertInstanceOf(User::class, $chat->dialogWithUser);
|
||||
$this->assertInstanceOf(UserWithPhoto::class, $chat->dialogWithUser);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -480,4 +532,260 @@ final class ModelFactoryTest extends TestCase
|
||||
$this->assertEmpty($list->members);
|
||||
$this->assertNull($list->marker);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function createVideoAttachmentDetailsSuccessfully(): void
|
||||
{
|
||||
$rawData = [
|
||||
'token' => 'vid_token',
|
||||
'width' => 1280,
|
||||
'height' => 720,
|
||||
'duration' => 60,
|
||||
'urls' => ['mp4_720' => 'http://a.com/720.mp4'],
|
||||
'thumbnail' => ['token' => 'thumb_token'],
|
||||
];
|
||||
|
||||
$details = $this->factory->createVideoAttachmentDetails($rawData);
|
||||
|
||||
$this->assertInstanceOf(VideoAttachmentDetails::class, $details);
|
||||
$this->assertSame('vid_token', $details->token);
|
||||
$this->assertInstanceOf(VideoUrls::class, $details->urls);
|
||||
$this->assertInstanceOf(PhotoAttachmentRequestPayload::class, $details->thumbnail);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function createMessageCorrectlyHydratesPolymorphicAttachments(): void
|
||||
{
|
||||
$rawData = [
|
||||
'timestamp' => time(),
|
||||
'body' => [
|
||||
'mid' => 'mid.789.def',
|
||||
'seq' => 102,
|
||||
'text' => 'Message with data attachment',
|
||||
'attachments' => [
|
||||
['type' => 'data', 'data' => 'payload_from_reply_button'],
|
||||
[
|
||||
'type' => 'share',
|
||||
'payload' => ['url' => 'http://a.com'],
|
||||
'title' => 'Test Share',
|
||||
'description' => null,
|
||||
'image_url' => null,
|
||||
],
|
||||
['type' => 'image', 'payload' => ['photo_id' => 1, 'token' => 't', 'url' => 'u']]
|
||||
],
|
||||
'markup' => null,
|
||||
],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => 123],
|
||||
|
||||
];
|
||||
|
||||
$message = $this->factory->createMessage($rawData);
|
||||
$attachments = $message->body->attachments;
|
||||
|
||||
$this->assertInstanceOf(Message::class, $message);
|
||||
$this->assertInstanceOf(MessageBody::class, $message->body);
|
||||
$this->assertIsArray($attachments);
|
||||
$this->assertCount(3, $attachments);
|
||||
|
||||
$this->assertInstanceOf(DataAttachment::class, $attachments[0]);
|
||||
$this->assertSame('payload_from_reply_button', $attachments[0]->data);
|
||||
|
||||
$this->assertInstanceOf(ShareAttachment::class, $message->body->attachments[1]);
|
||||
$this->assertSame('Test Share', $attachments[1]->title);
|
||||
|
||||
$this->assertInstanceOf(PhotoAttachment::class, $attachments[2]);
|
||||
$this->assertSame(1, $attachments[2]->payload->photoId);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function createMessageCorrectlyHydratesMarkup(): void
|
||||
{
|
||||
// ... (данные теста)
|
||||
$rawData = [
|
||||
'timestamp' => time(),
|
||||
'body' => [
|
||||
'mid' => 'mid.markup.test',
|
||||
'seq' => 200,
|
||||
'text' => 'Hello world! Visit our site.',
|
||||
'attachments' => null,
|
||||
'markup' => [
|
||||
['type' => 'strong', 'from' => 6, 'length' => 5],
|
||||
['type' => 'link', 'from' => 18, 'length' => 4, 'url' => 'https://dev.max.ru']
|
||||
]
|
||||
],
|
||||
'recipient' => ['chat_type' => 'dialog', 'user_id' => 123],
|
||||
];
|
||||
|
||||
$message = $this->factory->createMessage($rawData);
|
||||
$markup = $message->body->markup;
|
||||
|
||||
$this->assertInstanceOf(StrongMarkup::class, $markup[0]);
|
||||
$this->assertSame(6, $markup[0]->from);
|
||||
|
||||
$this->assertInstanceOf(LinkMarkup::class, $markup[1]);
|
||||
$this->assertSame('https://dev.max.ru', $markup[1]->url);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function createMarkupElementThrowsExceptionForUnknownType(): void
|
||||
{
|
||||
$this->expectException(LogicException::class);
|
||||
$this->expectExceptionMessage('Unknown or unsupported markup type: brand_new_unsupported_type');
|
||||
|
||||
$this->factory->createMarkupElement(['type' => 'brand_new_unsupported_type']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function createAttachmentCorrectlyHydratesReplyKeyboard(): void
|
||||
{
|
||||
$data = [
|
||||
'type' => 'reply_keyboard',
|
||||
'buttons' => [
|
||||
[['type' => 'message', 'text' => 'Hello']],
|
||||
[['type' => 'user_contact', 'text' => 'My Contact']]
|
||||
]
|
||||
];
|
||||
|
||||
$attachment = $this->factory->createAttachment($data);
|
||||
|
||||
$this->assertInstanceOf(ReplyKeyboardAttachment::class, $attachment);
|
||||
$this->assertCount(2, $attachment->buttons);
|
||||
$this->assertInstanceOf(SendMessageButton::class, $attachment->buttons[0][0]);
|
||||
$this->assertInstanceOf(SendContactButton::class, $attachment->buttons[1][0]);
|
||||
$this->assertSame('My Contact', $attachment->buttons[1][0]->text);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function createReplyButtonThrowsExceptionForUnknownType(): void
|
||||
{
|
||||
$this->expectException(LogicException::class);
|
||||
$this->expectExceptionMessage('Unknown or unsupported reply button type: new_fancy_button');
|
||||
|
||||
$this->factory->createReplyButton(['type' => 'new_fancy_button']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for successful inline button creation.
|
||||
* @return array<string, array{0: array<string, mixed>, 1: class-string}>
|
||||
*/
|
||||
public static function inlineButtonProvider(): array
|
||||
{
|
||||
return [
|
||||
'CallbackButton' => [
|
||||
['type' => 'callback', 'text' => 'Press', 'payload' => 'p'],
|
||||
CallbackButton::class,
|
||||
],
|
||||
'LinkButton' => [
|
||||
['type' => 'link', 'text' => 'Visit', 'url' => 'https://a.com'],
|
||||
LinkButton::class,
|
||||
],
|
||||
'RequestContactButton' => [
|
||||
['type' => 'request_contact', 'text' => 'Share Contact'],
|
||||
RequestContactButton::class,
|
||||
],
|
||||
'RequestGeoLocationButton' => [
|
||||
['type' => 'request_geo_location', 'text' => 'Share Location', 'quick' => false],
|
||||
RequestGeoLocationButton::class,
|
||||
],
|
||||
'ChatButton' => [
|
||||
['type' => 'chat', 'text' => 'Join Chat', 'chat_title' => 'My Chat'],
|
||||
ChatButton::class,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('inlineButtonProvider')]
|
||||
public function createInlineButtonSuccessfully(array $data, string $expectedClass): void
|
||||
{
|
||||
$button = $this->factory->createInlineButton($data);
|
||||
|
||||
$this->assertInstanceOf($expectedClass, $button);
|
||||
$this->assertSame($data['text'], $button->text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for invalid inline button data.
|
||||
* @return array<string, array{0: array<string, mixed>, 1: string}>
|
||||
*/
|
||||
public static function invalidInlineButtonProvider(): array
|
||||
{
|
||||
return [
|
||||
'unknown type' => [
|
||||
['type' => 'unknown_button_type', 'text' => 'Unknown'],
|
||||
'Unknown or unsupported inline button type: unknown_button_type',
|
||||
],
|
||||
'missing type' => [
|
||||
['text' => 'No type here'],
|
||||
'Unknown or unsupported inline button type: none',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('invalidInlineButtonProvider')]
|
||||
public function createInlineButtonThrowsExceptionForInvalidType(array $invalidData, string $expectedMessage): void
|
||||
{
|
||||
$this->expectException(LogicException::class);
|
||||
$this->expectExceptionMessage($expectedMessage);
|
||||
|
||||
$this->factory->createInlineButton($invalidData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testing various attachment types in createAttachment.
|
||||
* @return array<string, array{0: array<string, mixed>, 1: class-string, 2: callable}>
|
||||
*/
|
||||
public static function attachmentTypeProvider(): array
|
||||
{
|
||||
return [
|
||||
'Data Attachment' => [
|
||||
['type' => 'data', 'data' => 'test_payload'],
|
||||
DataAttachment::class,
|
||||
function (TestCase $test, DataAttachment $attachment) {
|
||||
$test->assertSame('test_payload', $attachment->data);
|
||||
}
|
||||
],
|
||||
|
||||
'Location Attachment' => [
|
||||
['type' => 'location', 'latitude' => 55.751244, 'longitude' => 37.618423],
|
||||
LocationAttachment::class,
|
||||
function (TestCase $test, LocationAttachment $attachment) {
|
||||
$test->assertSame(55.751244, $attachment->latitude);
|
||||
}
|
||||
],
|
||||
|
||||
'Inline Keyboard Attachment' => [
|
||||
[
|
||||
'type' => 'inline_keyboard',
|
||||
'payload' => [
|
||||
'buttons' => [
|
||||
[['type' => 'callback', 'text' => 'Test', 'payload' => 'p']]
|
||||
]
|
||||
]
|
||||
],
|
||||
InlineKeyboardAttachment::class,
|
||||
function (TestCase $test, InlineKeyboardAttachment $attachment) {
|
||||
$test->assertInstanceOf(KeyboardPayload::class, $attachment->payload);
|
||||
$test->assertIsArray($attachment->payload->buttons);
|
||||
$test->assertInstanceOf(CallbackButton::class, $attachment->payload->buttons[0][0]);
|
||||
$test->assertSame('p', $attachment->payload->buttons[0][0]->payload);
|
||||
}
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('attachmentTypeProvider')]
|
||||
public function createAttachmentSuccessfullyCreatesVariousTypes(
|
||||
array $data,
|
||||
string $expectedClass,
|
||||
callable $assertionCallback,
|
||||
): void {
|
||||
$attachment = $this->factory->createAttachment($data);
|
||||
|
||||
$this->assertInstanceOf($expectedClass, $attachment);
|
||||
|
||||
$assertionCallback($this, $attachment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@ namespace BushlanovDev\MaxMessengerBot\Tests\Models;
|
||||
use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
use DateTimeInterface;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
use stdClass;
|
||||
|
||||
#[CoversClass(AbstractModel::class)]
|
||||
@@ -176,6 +178,30 @@ final class AbstractModelMappingTest extends TestCase
|
||||
$this->assertEquals($expectedArray, $resultArray);
|
||||
$this->assertArrayNotHasKey('uninitialized_prop', $resultArray);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function castValueReturnsValueAsIsForUnmanagedScalarAssignedToObjectType(): void
|
||||
{
|
||||
$dateString = '2025-08-26';
|
||||
|
||||
$reflectionClass = new ReflectionClass(ModelForUnmanagedType::class);
|
||||
$abstractModelReflection = $reflectionClass->getParentClass();
|
||||
$castValueMethod = $abstractModelReflection->getMethod('castValue');
|
||||
$castValueMethod->setAccessible(true);
|
||||
|
||||
$property = $reflectionClass->getProperty('eventDate');
|
||||
|
||||
$result = $castValueMethod->invoke(null, $dateString, $property);
|
||||
|
||||
$this->assertSame($dateString, $result);
|
||||
}
|
||||
}
|
||||
|
||||
final readonly class ModelForUnmanagedType extends AbstractModel
|
||||
{
|
||||
public function __construct(public DateTimeInterface $eventDate)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
final readonly class DummyModelForUninitializedProperty extends AbstractModel
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\AudioAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\MediaAttachmentPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(AudioAttachment::class)]
|
||||
#[UsesClass(MediaAttachmentPayload::class)]
|
||||
final class AudioAttachmentTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedFromArray(): void
|
||||
{
|
||||
$data = ['type' => 'audio', 'payload' => ['url' => 'u', 'token' => 't'], 'transcription' => 'Hello world.'];
|
||||
$attachment = AudioAttachment::fromArray($data);
|
||||
$this->assertInstanceOf(AudioAttachment::class, $attachment);
|
||||
$this->assertSame('Hello world.', $attachment->transcription);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -2,11 +2,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons;
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\Intent;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\CallbackButton;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -22,7 +22,7 @@ final class CallbackButtonTest extends TestCase
|
||||
$expectedArray = [
|
||||
'payload' => 'test_payload',
|
||||
'intent' => Intent::Default->value,
|
||||
'type' => ButtonType::Callback->value,
|
||||
'type' => InlineButtonType::Callback->value,
|
||||
'text' => 'Test Button',
|
||||
];
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\ChatButton;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(ChatButton::class)]
|
||||
final class ChatButtonTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function toArrayWithRequiredParameters(): void
|
||||
{
|
||||
$button = new ChatButton(
|
||||
text: 'Discuss Topic',
|
||||
chatTitle: 'Topic Discussion',
|
||||
);
|
||||
|
||||
$expected = [
|
||||
'type' => 'chat',
|
||||
'text' => 'Discuss Topic',
|
||||
'chat_title' => 'Topic Discussion',
|
||||
'chat_description' => null,
|
||||
'start_payload' => null,
|
||||
'uuid' => null,
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $button->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function toArrayWithAllParameters(): void
|
||||
{
|
||||
$button = new ChatButton(
|
||||
text: 'Join Project Chat',
|
||||
chatTitle: 'Project Alpha Chat',
|
||||
chatDescription: 'Chat for team members of Project Alpha.',
|
||||
startPayload: 'project-alpha-join',
|
||||
uuid: 123456789
|
||||
);
|
||||
|
||||
$expected = [
|
||||
'type' => 'chat',
|
||||
'text' => 'Join Project Chat',
|
||||
'chat_title' => 'Project Alpha Chat',
|
||||
'chat_description' => 'Chat for team members of Project Alpha.',
|
||||
'start_payload' => 'project-alpha-join',
|
||||
'uuid' => 123456789,
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $button->toArray());
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -2,10 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons;
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\LinkButton;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\LinkButton;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -20,7 +20,7 @@ final class LinkButtonTest extends TestCase
|
||||
|
||||
$expectedArray = [
|
||||
'url' => 'https://example.com',
|
||||
'type' => ButtonType::Link->value,
|
||||
'type' => InlineButtonType::Link->value,
|
||||
'text' => 'Test Button',
|
||||
];
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\OpenAppButton;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(OpenAppButton::class)]
|
||||
final class OpenAppButtonTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function toArraySerializesCorrectly(): void
|
||||
{
|
||||
$button = new OpenAppButton('Test Button', 'MyWebApp', 123);
|
||||
|
||||
$expectedArray = [
|
||||
'web_app' => 'MyWebApp',
|
||||
'contact_id' => 123,
|
||||
'type' => InlineButtonType::OpenApp->value,
|
||||
'text' => 'Test Button',
|
||||
];
|
||||
|
||||
$resultArray = $button->toArray();
|
||||
|
||||
$this->assertSame($expectedArray, $resultArray);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function fromArrayHydratesCorrectly(): void
|
||||
{
|
||||
$data = [
|
||||
'type' => 'open_app',
|
||||
'text' => 'Launch',
|
||||
'web_app' => 'SomeApp',
|
||||
'contact_id' => 456,
|
||||
];
|
||||
|
||||
$button = OpenAppButton::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(OpenAppButton::class, $button);
|
||||
$this->assertSame(InlineButtonType::OpenApp, $button->type);
|
||||
$this->assertSame('Launch', $button->text);
|
||||
$this->assertSame('SomeApp', $button->webApp);
|
||||
$this->assertSame(456, $button->contactId);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -2,10 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons;
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\RequestContactButton;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\RequestContactButton;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -19,7 +19,7 @@ final class RequestContactTest extends TestCase
|
||||
$button = new RequestContactButton('Test Button');
|
||||
|
||||
$expectedArray = [
|
||||
'type' => ButtonType::RequestContact->value,
|
||||
'type' => InlineButtonType::RequestContact->value,
|
||||
'text' => 'Test Button',
|
||||
];
|
||||
|
||||
+5
-5
@@ -2,10 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons;
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons\Inline;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\RequestGeoLocationButton;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\RequestGeoLocationButton;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -20,7 +20,7 @@ final class RequestGeoLocationTest extends TestCase
|
||||
|
||||
$expectedArray = [
|
||||
'quick' => false,
|
||||
'type' => ButtonType::RequestGeoLocation->value,
|
||||
'type' => InlineButtonType::RequestGeoLocation->value,
|
||||
'text' => 'Test Button',
|
||||
];
|
||||
|
||||
@@ -36,7 +36,7 @@ final class RequestGeoLocationTest extends TestCase
|
||||
|
||||
$expectedArray = [
|
||||
'quick' => true,
|
||||
'type' => ButtonType::RequestGeoLocation->value,
|
||||
'type' => InlineButtonType::RequestGeoLocation->value,
|
||||
'text' => 'Test Button',
|
||||
];
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\OpenAppButton;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(OpenAppButton::class)]
|
||||
final class OpenAppButtonTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function toArraySerializesCorrectly(): void
|
||||
{
|
||||
$button = new OpenAppButton('Test Button', 'MyWebApp', 123);
|
||||
|
||||
$expectedArray = [
|
||||
'web_app' => 'MyWebApp',
|
||||
'contact_id' => 123,
|
||||
'type' => ButtonType::OpenApp->value,
|
||||
'text' => 'Test Button',
|
||||
];
|
||||
|
||||
$resultArray = $button->toArray();
|
||||
|
||||
$this->assertSame($expectedArray, $resultArray);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons\Reply;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendContactButton;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(SendContactButton::class)]
|
||||
final class SendContactButtonTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function toArray(): void
|
||||
{
|
||||
$button = new SendContactButton('Share My Contact');
|
||||
|
||||
$expected = [
|
||||
'type' => 'user_contact',
|
||||
'text' => 'Share My Contact',
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $button->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons\Reply;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendGeoLocationButton;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(SendGeoLocationButton::class)]
|
||||
final class SendGeoLocationButtonTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function toArrayWithDefaultQuick(): void
|
||||
{
|
||||
$button = new SendGeoLocationButton('Share Location');
|
||||
|
||||
$expected = [
|
||||
'type' => 'user_geo_location',
|
||||
'text' => 'Share Location',
|
||||
'quick' => false,
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $button->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function toArrayWithQuickTrue(): void
|
||||
{
|
||||
$button = new SendGeoLocationButton('Quick Share', true);
|
||||
|
||||
$expected = [
|
||||
'type' => 'user_geo_location',
|
||||
'text' => 'Quick Share',
|
||||
'quick' => true,
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $button->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Buttons\Reply;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\Intent;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendMessageButton;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(SendMessageButton::class)]
|
||||
final class SendMessageButtonTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function toArrayWithDefaults(): void
|
||||
{
|
||||
$button = new SendMessageButton('Click Me');
|
||||
|
||||
$expected = [
|
||||
'type' => 'message',
|
||||
'text' => 'Click Me',
|
||||
'payload' => null,
|
||||
'intent' => 'default',
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $button->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function toArrayWithAllParameters(): void
|
||||
{
|
||||
$button = new SendMessageButton('Confirm', 'confirm-action-123', Intent::Positive);
|
||||
|
||||
$expected = [
|
||||
'type' => 'message',
|
||||
'text' => 'Confirm',
|
||||
'payload' => 'confirm-action-123',
|
||||
'intent' => 'positive',
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $button->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\ContactAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ContactAttachmentPayload;
|
||||
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(ContactAttachment::class)]
|
||||
#[UsesClass(ContactAttachmentPayload::class)]
|
||||
#[UsesClass(User::class)]
|
||||
final class ContactAttachmentTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedFromArray(): void
|
||||
{
|
||||
$data = ['type' => 'contact', 'payload' => ['vcf_info' => 'vcf', 'max_info' => null]];
|
||||
$attachment = ContactAttachment::fromArray($data);
|
||||
$this->assertInstanceOf(ContactAttachment::class, $attachment);
|
||||
$this->assertSame('vcf', $attachment->payload->vcfInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\AbstractAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\DataAttachment;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(DataAttachment::class)]
|
||||
#[UsesClass(AbstractAttachment::class)]
|
||||
final class DataAttachmentTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedAndSerialized(): void
|
||||
{
|
||||
$attachment = new DataAttachment('some_payload_from_button');
|
||||
|
||||
$this->assertSame(AttachmentType::Data, $attachment->type);
|
||||
$this->assertSame('some_payload_from_button', $attachment->data);
|
||||
|
||||
$expectedArray = [
|
||||
'type' => 'data',
|
||||
'data' => 'some_payload_from_button',
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedArray, $attachment->toArray());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function canBeCreatedFromArray(): void
|
||||
{
|
||||
$data = [
|
||||
'type' => 'data',
|
||||
'data' => 'payload123',
|
||||
];
|
||||
|
||||
$attachment = DataAttachment::fromArray($data);
|
||||
|
||||
$this->assertInstanceOf(DataAttachment::class, $attachment);
|
||||
$this->assertSame(AttachmentType::Data, $attachment->type);
|
||||
$this->assertSame('payload123', $attachment->data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\FileAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\FileAttachmentPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(FileAttachment::class)]
|
||||
#[UsesClass(FileAttachmentPayload::class)]
|
||||
final class FileAttachmentTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedFromArray(): void
|
||||
{
|
||||
$data = [
|
||||
'type' => 'file',
|
||||
'payload' => ['url' => 'u', 'token' => 't'],
|
||||
'filename' => 'doc.pdf',
|
||||
'size' => 1024,
|
||||
];
|
||||
$attachment = FileAttachment::fromArray($data);
|
||||
$this->assertInstanceOf(FileAttachment::class, $attachment);
|
||||
$this->assertSame('doc.pdf', $attachment->filename);
|
||||
$this->assertSame(1024, $attachment->size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\InlineKeyboardAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\KeyboardPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(InlineKeyboardAttachment::class)]
|
||||
#[UsesClass(KeyboardPayload::class)]
|
||||
#[UsesClass(CallbackButton::class)]
|
||||
final class InlineKeyboardAttachmentTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedFromArray(): void
|
||||
{
|
||||
$data = [
|
||||
'type' => 'inline_keyboard',
|
||||
'payload' => ['buttons' => [[['type' => 'callback', 'text' => 'Press', 'payload' => 'p']]]],
|
||||
];
|
||||
$attachment = InlineKeyboardAttachment::fromArray($data);
|
||||
$this->assertInstanceOf(InlineKeyboardAttachment::class, $attachment);
|
||||
$this->assertInstanceOf(KeyboardPayload::class, $attachment->payload);
|
||||
$this->assertCount(1, $attachment->payload->buttons);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\LocationAttachment;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(LocationAttachment::class)]
|
||||
final class LocationAttachmentTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedFromArray(): void
|
||||
{
|
||||
$data = ['type' => 'location', 'latitude' => 55.75, 'longitude' => 37.61];
|
||||
$attachment = LocationAttachment::fromArray($data);
|
||||
$this->assertInstanceOf(LocationAttachment::class, $attachment);
|
||||
$this->assertSame(55.75, $attachment->latitude);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ContactAttachmentPayload;
|
||||
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(ContactAttachmentPayload::class)]
|
||||
#[UsesClass(User::class)]
|
||||
final class ContactAttachmentPayloadTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedAndSerialized(): void
|
||||
{
|
||||
$user = User::fromArray(
|
||||
['user_id' => 101, 'first_name' => 'MaxUser', 'is_bot' => false, 'last_activity_time' => time()]
|
||||
);
|
||||
$payload = new ContactAttachmentPayload('vcf_info_string', $user);
|
||||
$this->assertSame('vcf_info_string', $payload->vcfInfo);
|
||||
$this->assertSame($user, $payload->maxInfo);
|
||||
$this->assertEquals(['vcf_info' => 'vcf_info_string', 'max_info' => $user->toArray()], $payload->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\FileAttachmentPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(FileAttachmentPayload::class)]
|
||||
final class FileAttachmentPayloadTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedAndSerialized(): void
|
||||
{
|
||||
$payload = new FileAttachmentPayload('https://example.com/doc.pdf', 'file_token_456');
|
||||
$this->assertSame('https://example.com/doc.pdf', $payload->url);
|
||||
$this->assertSame('file_token_456', $payload->token);
|
||||
$this->assertEquals(['url' => 'https://example.com/doc.pdf', 'token' => 'file_token_456'], $payload->toArray());
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\Intent;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\LinkButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\LinkButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardAttachmentRequestPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
@@ -49,7 +49,7 @@ final class InlineKeyboardPayloadTest extends TestCase
|
||||
'buttons' => [
|
||||
[
|
||||
[
|
||||
'type' => ButtonType::Callback->value,
|
||||
'type' => InlineButtonType::Callback->value,
|
||||
'text' => 'Accept',
|
||||
'payload' => 'accept_payload',
|
||||
'intent' => Intent::Positive->value,
|
||||
@@ -57,13 +57,13 @@ final class InlineKeyboardPayloadTest extends TestCase
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => ButtonType::Callback->value,
|
||||
'type' => InlineButtonType::Callback->value,
|
||||
'text' => 'Decline',
|
||||
'payload' => 'decline_payload',
|
||||
'intent' => Intent::Negative->value,
|
||||
],
|
||||
[
|
||||
'type' => ButtonType::Link->value,
|
||||
'type' => InlineButtonType::Link->value,
|
||||
'text' => 'Help',
|
||||
'url' => 'https://example.com/help',
|
||||
],
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\ModelFactory;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\AbstractAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\AbstractInlineButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\LinkButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\InlineKeyboardAttachment;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\KeyboardPayload;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(KeyboardPayload::class)]
|
||||
#[UsesClass(ModelFactory::class)]
|
||||
#[UsesClass(AbstractAttachment::class)]
|
||||
#[UsesClass(AbstractInlineButton::class)]
|
||||
#[UsesClass(CallbackButton::class)]
|
||||
#[UsesClass(LinkButton::class)]
|
||||
#[UsesClass(InlineKeyboardAttachment::class)]
|
||||
final class KeyboardPayloadTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function canBeCreatedFromArrayViaFactory(): void
|
||||
{
|
||||
$data = [
|
||||
'type' => 'inline_keyboard',
|
||||
'payload' => [
|
||||
'buttons' => [
|
||||
[
|
||||
['type' => 'callback', 'text' => 'Press Me', 'payload' => 'payload_123'],
|
||||
],
|
||||
[
|
||||
['type' => 'link', 'text' => 'Docs', 'url' => 'https://dev.max.ru'],
|
||||
['type' => 'callback', 'text' => 'Cancel', 'payload' => 'cancel_op'],
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$factory = new ModelFactory();
|
||||
$attachment = $factory->createAttachment($data);
|
||||
|
||||
$this->assertInstanceOf(InlineKeyboardAttachment::class, $attachment);
|
||||
$this->assertInstanceOf(KeyboardPayload::class, $attachment->payload);
|
||||
|
||||
$buttons = $attachment->payload->buttons;
|
||||
$this->assertCount(2, $buttons);
|
||||
|
||||
$this->assertCount(1, $buttons[0]);
|
||||
$this->assertInstanceOf(CallbackButton::class, $buttons[0][0]);
|
||||
$this->assertSame('payload_123', $buttons[0][0]->payload);
|
||||
|
||||
$this->assertCount(2, $buttons[1]);
|
||||
$this->assertInstanceOf(LinkButton::class, $buttons[1][0]);
|
||||
$this->assertSame('https://dev.max.ru', $buttons[1][0]->url);
|
||||
$this->assertInstanceOf(CallbackButton::class, $buttons[1][1]);
|
||||
$this->assertSame('cancel_op', $buttons[1][1]->payload);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user