diff --git a/README.md b/README.md index ce73e70..90c2f19 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,8 @@ $handler->handle(); #### Bots - [x] `GET /me` (`getBotInfo`) - [*Получение информации о боте.*](./docs/README.md#Получение-информации-о-боте) -- [x] `PATCH /me` (`editBotInfo`) - [*Редактирование информации о боте.*](./docs/README.md#Редактирование-информации-о-боте) +- [x] `PATCH /me/commands` (`editBotCommands`) - [*Редактирование команд бота.*](./docs/README.md#Редактирование-команд-бота) +- [x] ~~`PATCH /me` (`editBotInfo`) - [*Редактирование информации о боте.*](./docs/README.md#Редактирование-информации-о-боте-deprecated)~~ (deprecated) #### Chats diff --git a/docs/README.md b/docs/README.md index 4dae330..b53b0e3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,8 @@ - [Инициализация бота](#Инициализация-бота) - [Информация о боте](#Информация-о-боте) - `GET /me` (`getBotInfo`) - [*Получение информации о боте.*](#Получение-информации-о-боте) - - `PATCH /me` (`editBotInfo`) - [*Редактирование информации о боте.*](#Редактирование-информации-о-боте) + - `PATCH /me/commands` (`editBotCommands`) - [*Редактирование команд бота.*](#Редактирование-команд-бота) + - ~~`PATCH /me` (`editBotInfo`)~~ - [*Редактирование информации о боте.*](#Редактирование-информации-о-боте-deprecated) (deprecated) - [Чаты](#Чаты) - ~~`GET /chats` (`getChats`)~~ - [*Получение списка всех чатов бота.*](#Получение-списка-всех-чатов-бота-deprecated) (deprecated) - `GET /chats/{chatLink}` (`getChatByLink`) - [*Получение информации о чате по ссылке.*](#Получение-информации-о-чате-по-ссылке) @@ -119,7 +120,22 @@ $api = new BushlanovDev\MaxMessengerBot\Api( $botInfo = $api->getBotInfo(); ``` -### Редактирование информации о боте +### Редактирование команд бота + +Задаёт команды, которые пользователь видит подсказками, когда набирает «/». Не больше 32 команд, описание команды +необязательно. + +```php +$botCommands = $api->editBotCommands([ + new BotCommand('start', 'Начать'), + new BotCommand('help', 'Помощь'), +]); +``` + +### Редактирование информации о боте (deprecated) + +API больше не принимает `PATCH /me` и отвечает `Path /me is not recognized`. Команды задаются методом +[`editBotCommands`](#Редактирование-команд-бота), имя, описание и аватар бота — на платформе MAX для партнёров. Обратите внимание, что данный метод отправляется PATCH запросом. Это значит, что будут обновлены только переданные поля. diff --git a/src/Api.php b/src/Api.php index 59ffaef..f5ca7b3 100644 --- a/src/Api.php +++ b/src/Api.php @@ -17,6 +17,8 @@ use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\AudioAttachmentRequ use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\FileAttachmentRequest; use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\PhotoAttachmentRequest; use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\VideoAttachmentRequest; +use BushlanovDev\MaxMessengerBot\Models\BotCommand; +use BushlanovDev\MaxMessengerBot\Models\BotCommandsInfo; use BushlanovDev\MaxMessengerBot\Models\BotInfo; use BushlanovDev\MaxMessengerBot\Models\BotPatch; use BushlanovDev\MaxMessengerBot\Models\Chat; @@ -61,6 +63,7 @@ class Api private const string METHOD_PUT = 'PUT'; private const string ACTION_ME = '/me'; + private const string ACTION_ME_COMMANDS = '/me/commands'; private const string ACTION_SUBSCRIPTIONS = '/subscriptions'; private const string ACTION_MESSAGES = '/messages'; private const string ACTION_UPLOADS = '/uploads'; @@ -252,6 +255,31 @@ class Api ); } + /** + * Edits the bot commands shown to users as hints when they type "/". + * + * Example: editBotCommands([new BotCommand('start', 'Start the bot'), new BotCommand('help', 'Help')]); + * + * @param BotCommand[] $commands Up to 32 commands. + * + * @return BotCommandsInfo + * @throws ClientApiException + * @throws NetworkException + * @throws ReflectionException + * @throws SerializationException + */ + public function editBotCommands(array $commands): BotCommandsInfo + { + return $this->modelFactory->createBotCommandsInfo( + $this->client->request( + self::METHOD_PATCH, + self::ACTION_ME_COMMANDS, + [], + ['commands' => array_map(static fn(BotCommand $command) => $command->toArray(), $commands)], + ) + ); + } + /** * List of all active webhook subscriptions. * @@ -1099,6 +1127,10 @@ class Api * * Example: editBotInfo(new BotPatch(name: 'New Bot Name', description: null)); * + * @deprecated The API no longer accepts `PATCH /me` and answers "Path /me is not recognized". + * Set commands with editBotCommands(); name, description and photo are edited + * on the MAX partner platform. + * * @param BotPatch $botPatch * * @return BotInfo diff --git a/src/ModelFactory.php b/src/ModelFactory.php index b17c508..1f37042 100644 --- a/src/ModelFactory.php +++ b/src/ModelFactory.php @@ -32,6 +32,7 @@ use BushlanovDev\MaxMessengerBot\Models\Attachments\PhotoAttachment; use BushlanovDev\MaxMessengerBot\Models\Attachments\ShareAttachment; use BushlanovDev\MaxMessengerBot\Models\Attachments\StickerAttachment; use BushlanovDev\MaxMessengerBot\Models\Attachments\VideoAttachment; +use BushlanovDev\MaxMessengerBot\Models\BotCommandsInfo; use BushlanovDev\MaxMessengerBot\Models\BotInfo; use BushlanovDev\MaxMessengerBot\Models\Chat; use BushlanovDev\MaxMessengerBot\Models\ChatList; @@ -106,6 +107,19 @@ readonly class ModelFactory return Result::fromArray($data); } + /** + * Commands of the current bot. + * + * @param array $data + * + * @return BotCommandsInfo + * @throws ReflectionException + */ + public function createBotCommandsInfo(array $data): BotCommandsInfo + { + return BotCommandsInfo::fromArray($data); + } + /** * Information about the current bot. * diff --git a/src/Models/BotCommandsInfo.php b/src/Models/BotCommandsInfo.php new file mode 100644 index 0000000..823ec21 --- /dev/null +++ b/src/Models/BotCommandsInfo.php @@ -0,0 +1,22 @@ +assertSame($expectedResult, $result); } + #[Test] + public function editBotCommandsSendsCommandsToDedicatedEndpoint(): void + { + $rawResponseData = [ + 'commands' => [ + ['name' => 'start', 'description' => 'Start the bot'], + ['name' => 'help', 'description' => null], + ], + ]; + $expectedResult = new BotCommandsInfo([ + new BotCommand('start', 'Start the bot'), + new BotCommand('help', null), + ]); + + $this->clientMock + ->expects($this->once()) + ->method('request') + ->with('PATCH', '/me/commands', [], $rawResponseData) + ->willReturn($rawResponseData); + + $this->modelFactoryMock + ->expects($this->once()) + ->method('createBotCommandsInfo') + ->with($rawResponseData) + ->willReturn($expectedResult); + + $result = $this->api->editBotCommands([ + new BotCommand('start', 'Start the bot'), + new BotCommand('help', null), + ]); + + $this->assertSame($expectedResult, $result); + } + + #[Test] + public function editBotCommandsSendsAnEmptyList(): void + { + $this->clientMock + ->expects($this->once()) + ->method('request') + ->with('PATCH', '/me/commands', [], ['commands' => []]) + ->willReturn(['commands' => []]); + + $this->modelFactoryMock + ->expects($this->once()) + ->method('createBotCommandsInfo') + ->with(['commands' => []]) + ->willReturn(new BotCommandsInfo([])); + + $this->api->editBotCommands([]); + } + #[Test] public function editBotInfoSendsCorrectPatchBody(): void { diff --git a/tests/ModelFactoryTest.php b/tests/ModelFactoryTest.php index 5050a2a..3938144 100644 --- a/tests/ModelFactoryTest.php +++ b/tests/ModelFactoryTest.php @@ -27,6 +27,7 @@ use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\ShareAttachmentRequ use BushlanovDev\MaxMessengerBot\Models\Attachments\PhotoAttachment; use BushlanovDev\MaxMessengerBot\Models\Attachments\ShareAttachment; use BushlanovDev\MaxMessengerBot\Models\BotCommand; +use BushlanovDev\MaxMessengerBot\Models\BotCommandsInfo; use BushlanovDev\MaxMessengerBot\Models\BotInfo; use BushlanovDev\MaxMessengerBot\Models\Chat; use BushlanovDev\MaxMessengerBot\Models\ChatList; @@ -61,6 +62,7 @@ use Psr\Log\LoggerInterface; #[CoversClass(ModelFactory::class)] #[UsesClass(BotInfo::class)] #[UsesClass(BotCommand::class)] +#[UsesClass(BotCommandsInfo::class)] #[UsesClass(Result::class)] #[UsesClass(Subscription::class)] #[UsesClass(ArrayOf::class)] @@ -171,6 +173,23 @@ final class ModelFactoryTest extends TestCase $this->assertSame('help', $botInfo->commands[1]->name); } + #[Test] + public function createBotCommandsInfoHydratesCommands(): void + { + $botCommands = $this->factory->createBotCommandsInfo([ + 'commands' => [ + ['name' => 'start', 'description' => 'Start the bot'], + ['name' => 'help', 'description' => null], + ], + ]); + + $this->assertInstanceOf(BotCommandsInfo::class, $botCommands); + $this->assertCount(2, $botCommands->commands); + $this->assertInstanceOf(BotCommand::class, $botCommands->commands[0]); + $this->assertSame('start', $botCommands->commands[0]->name); + $this->assertNull($botCommands->commands[1]->description); + } + #[Test] public function createBotInfoHandlesNullCommands(): void { diff --git a/tests/Models/BotCommandsInfoTest.php b/tests/Models/BotCommandsInfoTest.php new file mode 100644 index 0000000..d9fe9f2 --- /dev/null +++ b/tests/Models/BotCommandsInfoTest.php @@ -0,0 +1,48 @@ + [ + ['name' => 'start', 'description' => 'Start the bot'], + ], + ]); + + $this->assertCount(1, $info->commands); + $this->assertInstanceOf(BotCommand::class, $info->commands[0]); + $this->assertSame('start', $info->commands[0]->name); + $this->assertSame('Start the bot', $info->commands[0]->description); + } + + #[Test] + public function commandsAreNullWhenTheBotHasNone(): void + { + $this->assertNull(BotCommandsInfo::fromArray([])->commands); + } + + #[Test] + public function toArraySerializesCommands(): void + { + $info = new BotCommandsInfo([new BotCommand('help', 'Help')]); + + $this->assertSame(['commands' => [['name' => 'help', 'description' => 'Help']]], $info->toArray()); + } +}