Skip to content

Commit 06492a6

Browse files
committed
Added BotRemovedFromChatUpdate & ChatTitleChangedUpdate & MessageChatCreatedUpdate & UserAddedToChatUpdate & UserRemovedFromChatUpdate
1 parent d5b950b commit 06492a6

16 files changed

+1013
-42
lines changed

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
[![PHP version](https://img.shields.io/badge/php-%3E%3D%208.3-8892BF.svg?style=flat-square)](https://github.com/BushlanovDev/max-bot-api-client-php)
55
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
66

7+
> [!CAUTION]
8+
> На мой взгляд `Max Messenger` является ни чем иным как малварью, созданной для слежки за гражданами РФ.
9+
> Настоятельно не рекомендую использовать свой настоящий номер телефона, на реальных устройствах, и для личной переписки.
710
811
> [!IMPORTANT]
912
> Библиотека в стадии активной разработки.
@@ -16,3 +19,51 @@
1619

1720
Откройте диалог с [MasterBot](https://max.ru/MasterBot), следуйте инструкциям и создайте нового бота. После создания
1821
бота MasterBot отправит вам токен.
22+
23+
## Реализованные методы
24+
25+
#### Bots
26+
27+
- `[x] GET /me` (`getBotInfo`) — *Получение информации о боте.*
28+
- `[ ] PATCH /me` (`editBotInfo`) — *Редактирование информации о боте.*
29+
30+
#### Chats
31+
32+
- `[ ] GET /chats` (`getChats`) — *Получение списка всех чатов бота.*
33+
- `[ ] GET /chats/{chatLink}` (`getChatByLink`) — *Получение информации о чате по ссылке.*
34+
- `[x] GET /chats/{chatId}` (`getChat`) — *Получение информации о чате по ID.*
35+
- `[ ] PATCH /chats/{chatId}` (`editChat`) — *Редактирование информации о чате.*
36+
- `[ ] DELETE /chats/{chatId}` (`deleteChat`) — *Удаление чата.*
37+
- `[ ] POST /chats/{chatId}/actions` (`sendAction`) — *Отправка действия в чат (например, "печатает...").*
38+
- `[ ] GET /chats/{chatId}/pin` (`getPinnedMessage`) — *Получение закрепленного сообщения.*
39+
- `[ ] PUT /chats/{chatId}/pin` (`pinMessage`) — *Закрепление сообщения.*
40+
- `[ ] DELETE /chats/{chatId}/pin` (`unpinMessage`) — *Открепление сообщения.*
41+
- `[ ] GET /chats/{chatId}/members/me` (`getMembership`) — *Получение информации о членстве бота в чате.*
42+
- `[ ] DELETE /chats/{chatId}/members/me` (`leaveChat`) — *Выход бота из чата.*
43+
- `[ ] GET /chats/{chatId}/members/admins` (`getAdmins`) — *Получение администраторов чата.*
44+
- `[ ] POST /chats/{chatId}/members/admins` (`postAdmins`) — *Назначение администраторов чата.*
45+
- `[ ] DELETE /chats/{chatId}/members/admins/{userId}` (`deleteAdmins`) — *Снятие прав администратора.*
46+
- `[ ] GET /chats/{chatId}/members` (`getMembers`) — *Получение участников чата.*
47+
- `[ ] POST /chats/{chatId}/members` (`addMembers`) — *Добавление участников в чат.*
48+
- `[ ] DELETE /chats/{chatId}/members` (`removeMember`) — *Удаление участника из чата.*
49+
50+
#### Subscriptions
51+
52+
- `[x] GET /subscriptions` (`getSubscriptions`) — *Получение списка Webhook-подписок.*
53+
- `[x] POST /subscriptions` (`subscribe`) — *Создание Webhook-подписки.*
54+
- `[x] DELETE /subscriptions` (`unsubscribe`) — *Удаление Webhook-подписки.*
55+
- `[x] GET /updates` (`getUpdates`) — *Получение обновлений через Long-Polling.*
56+
57+
#### Upload
58+
59+
- `[x] POST /uploads` (`getUploadUrl`) — *Получение URL для загрузки файла.*
60+
61+
#### Messages
62+
63+
- `[ ] GET /messages` (`getMessages`) — *Получение списка сообщений из чата.*
64+
- `[x] POST /messages` (`sendMessage`) — *Отправка сообщения.*
65+
- `[ ] PUT /messages` (`editMessage`) — *Редактирование сообщения.*
66+
- `[ ] DELETE /messages` (`deleteMessage`) — *Удаление сообщения.*
67+
- `[ ] GET /messages/{messageId}` (`getMessageById`) — *Получение сообщения по ID.*
68+
- `[ ] GET /videos/{videoToken}` (`getVideoAttachmentDetails`) — *Получение детальной информации о видео.*
69+
- `[ ] POST /answers` (`answerOnCallback`) — *Ответ на нажатие callback-кнопки.*

src/Api.php

Lines changed: 143 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use BushlanovDev\MaxMessengerBot\Enums\UploadType;
1010
use BushlanovDev\MaxMessengerBot\Exceptions\ClientApiException;
1111
use BushlanovDev\MaxMessengerBot\Exceptions\NetworkException;
12+
use BushlanovDev\MaxMessengerBot\Exceptions\SecurityException;
1213
use BushlanovDev\MaxMessengerBot\Exceptions\SerializationException;
1314
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
1415
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\AbstractAttachmentRequest;
@@ -20,9 +21,11 @@
2021
use BushlanovDev\MaxMessengerBot\Models\Result;
2122
use BushlanovDev\MaxMessengerBot\Models\Subscription;
2223
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
24+
use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
2325
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
2426
use InvalidArgumentException;
2527
use LogicException;
28+
use Psr\Http\Message\ServerRequestInterface;
2629
use ReflectionException;
2730
use RuntimeException;
2831

@@ -105,6 +108,146 @@ public function createWebhookHandler(?string $secret = null): WebhookHandler
105108
return new WebhookHandler($this, $this->modelFactory, $secret);
106109
}
107110

111+
/**
112+
* Parses an incoming webhook request and returns a single Update object.
113+
* This is an alternative to the event-driven WebhookHandler::handle() method,
114+
* allowing for manual processing of updates.
115+
*
116+
* @param string|null $secret The secret key to verify the request signature.
117+
* @param ServerRequestInterface|null $request The PSR-7 request object. If null, it's created from globals.
118+
*
119+
* @return AbstractUpdate The parsed update object (e.g., MessageCreatedUpdate).
120+
* @throws \ReflectionException
121+
* @throws SecurityException
122+
* @throws SerializationException
123+
* @throws \LogicException
124+
*/
125+
public function getWebhookUpdate(?string $secret = null, ?ServerRequestInterface $request = null): AbstractUpdate
126+
{
127+
return $this->createWebhookHandler($secret)->getUpdate($request);
128+
}
129+
130+
/**
131+
* A simple way to process a single incoming webhook request using callbacks.
132+
* This method creates a WebhookHandler, registers the provided callbacks, and processes the request.
133+
*
134+
* @param array<string, callable> $handlers An associative array where keys are UpdateType string values
135+
* (e.g., UpdateType::MessageCreated->value) and values are handlers.
136+
* @param string|null $secret The secret key for request verification.
137+
* @param ServerRequestInterface|null $request The PSR-7 request object.
138+
*
139+
* @throws SecurityException
140+
* @throws SerializationException
141+
* @throws ReflectionException
142+
* @throws LogicException
143+
*/
144+
public function handleWebhooks(
145+
array $handlers,
146+
?string $secret = null,
147+
?ServerRequestInterface $request = null,
148+
): void {
149+
$webhookHandler = $this->createWebhookHandler($secret);
150+
151+
foreach ($handlers as $updateType => $callback) {
152+
$updateType = UpdateType::tryFrom($updateType);
153+
// @phpstan-ignore-next-line
154+
if ($updateType && is_callable($callback)) {
155+
$webhookHandler->addHandler($updateType, $callback);
156+
}
157+
}
158+
159+
$webhookHandler->handle($request);
160+
}
161+
162+
/**
163+
* You can use this method for getting updates in case your bot is not subscribed to WebHook.
164+
* The method is based on long polling.
165+
*
166+
* @param int|null $limit Maximum number of updates to be retrieved (1-1000).
167+
* @param int|null $timeout Timeout in seconds for long polling (0-90).
168+
* @param int|null $marker Pass `null` to get updates you didn't get yet.
169+
* @param UpdateType[]|null $types Comma separated list of update types your bot want to receive.
170+
*
171+
* @return UpdateList
172+
* @throws ClientApiException
173+
* @throws NetworkException
174+
* @throws ReflectionException
175+
* @throws SerializationException
176+
*/
177+
public function getUpdates(
178+
?int $limit = null,
179+
?int $timeout = null,
180+
?int $marker = null,
181+
?array $types = null,
182+
): UpdateList {
183+
$query = [
184+
'limit' => $limit,
185+
'timeout' => $timeout,
186+
'marker' => $marker,
187+
'types' => $types !== null ? implode(',', array_map(fn($type) => $type->value, $types)) : null,
188+
];
189+
190+
return $this->modelFactory->createUpdateList(
191+
$this->client->request(
192+
self::METHOD_GET,
193+
self::ACTION_UPDATES,
194+
array_filter($query, fn($value) => $value !== null),
195+
)
196+
);
197+
}
198+
199+
/**
200+
* Starts a long-polling loop to process updates using callbacks.
201+
* This method will run indefinitely until the script is terminated.
202+
*
203+
* @param array<string, callable> $handlers An associative array where keys are UpdateType enums
204+
* and values are the corresponding handler functions.
205+
* @param int|null $timeout Timeout in seconds for long polling (0-90). Defaults to 90.
206+
* @param int|null $marker Pass `null` to get updates you didn't get yet.
207+
*/
208+
public function handleUpdates(array $handlers, ?int $timeout = null, ?int $marker = null): void
209+
{
210+
// @phpstan-ignore-next-line
211+
while (true) {
212+
try {
213+
$this->processUpdatesBatch($handlers, $timeout, $marker);
214+
} catch (NetworkException $e) {
215+
error_log("Network error: " . $e->getMessage());
216+
sleep(5);
217+
} catch (\Exception $e) {
218+
error_log("An error occurred: " . $e->getMessage());
219+
sleep(1);
220+
}
221+
}
222+
}
223+
224+
/**
225+
* Processes a single batch of updates. This is the core logic used by handleUpdates().
226+
* Useful for custom loop implementations or for testing.
227+
*
228+
* @param array<string, callable> $handlers An associative array of update handlers.
229+
* @param int|null $timeout Timeout for the getUpdates call.
230+
* @param int|null $marker The marker for which updates to fetch.
231+
*
232+
* @throws ClientApiException
233+
* @throws NetworkException
234+
* @throws ReflectionException
235+
* @throws SerializationException
236+
*/
237+
public function processUpdatesBatch(array $handlers, ?int $timeout, ?int &$marker = null): void
238+
{
239+
$updateList = $this->getUpdates(timeout: $timeout, marker: $marker);
240+
241+
foreach ($updateList->updates as $update) {
242+
$handler = $handlers[$update->updateType->value] ?? null;
243+
if ($handler) {
244+
$handler($update, $this);
245+
}
246+
}
247+
248+
$marker = $updateList->marker;
249+
}
250+
108251
/**
109252
* Information about the current bot, identified by an access token.
110253
*
@@ -330,41 +473,4 @@ public function getChat(int $chatId): Chat
330473
$this->client->request(self::METHOD_GET, self::ACTION_CHATS . '/' . $chatId)
331474
);
332475
}
333-
334-
/**
335-
* You can use this method for getting updates in case your bot is not subscribed to WebHook.
336-
* The method is based on long polling.
337-
*
338-
* @param int|null $limit Maximum number of updates to be retrieved (1-1000).
339-
* @param int|null $timeout Timeout in seconds for long polling (0-90).
340-
* @param int|null $marker Pass `null` to get updates you didn't get yet.
341-
* @param UpdateType[]|null $types Comma separated list of update types your bot want to receive.
342-
*
343-
* @return UpdateList
344-
* @throws ClientApiException
345-
* @throws NetworkException
346-
* @throws ReflectionException
347-
* @throws SerializationException
348-
*/
349-
public function getUpdates(
350-
?int $limit = null,
351-
?int $timeout = null,
352-
?int $marker = null,
353-
?array $types = null,
354-
): UpdateList {
355-
$query = [
356-
'limit' => $limit,
357-
'timeout' => $timeout,
358-
'marker' => $marker,
359-
'types' => $types !== null ? implode(',', array_map(fn($type) => $type->value, $types)) : null,
360-
];
361-
362-
return $this->modelFactory->createUpdateList(
363-
$this->client->request(
364-
self::METHOD_GET,
365-
self::ACTION_UPDATES,
366-
array_filter($query, fn($value) => $value !== null),
367-
)
368-
);
369-
}
370476
}

src/ModelFactory.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@
1313
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
1414
use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
1515
use BushlanovDev\MaxMessengerBot\Models\Updates\BotAddedToChatUpdate;
16+
use BushlanovDev\MaxMessengerBot\Models\Updates\BotRemovedFromChatUpdate;
1617
use BushlanovDev\MaxMessengerBot\Models\Updates\BotStartedUpdate;
18+
use BushlanovDev\MaxMessengerBot\Models\Updates\ChatTitleChangedUpdate;
1719
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCallbackUpdate;
20+
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageChatCreatedUpdate;
1821
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCreatedUpdate;
1922
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageEditedUpdate;
2023
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageRemovedUpdate;
24+
use BushlanovDev\MaxMessengerBot\Models\Updates\UserAddedToChatUpdate;
25+
use BushlanovDev\MaxMessengerBot\Models\Updates\UserRemovedFromChatUpdate;
2126
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
2227
use LogicException;
2328
use ReflectionException;
@@ -162,7 +167,12 @@ public function createUpdate(array $data): AbstractUpdate
162167
UpdateType::MessageEdited => MessageEditedUpdate::fromArray($data),
163168
UpdateType::MessageRemoved => MessageRemovedUpdate::fromArray($data),
164169
UpdateType::BotAdded => BotAddedToChatUpdate::fromArray($data),
170+
UpdateType::BotRemoved => BotRemovedFromChatUpdate::fromArray($data),
171+
UpdateType::UserAdded => UserAddedToChatUpdate::fromArray($data),
172+
UpdateType::UserRemoved => UserRemovedFromChatUpdate::fromArray($data),
165173
UpdateType::BotStarted => BotStartedUpdate::fromArray($data),
174+
UpdateType::ChatTitleChanged => ChatTitleChangedUpdate::fromArray($data),
175+
UpdateType::MessageChatCreated => MessageChatCreatedUpdate::fromArray($data),
166176
default => throw new LogicException(
167177
'Unknown or unsupported update type received: ' . ($data['update_type'] ?? 'none')
168178
),
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BushlanovDev\MaxMessengerBot\Models\Updates;
6+
7+
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
8+
use BushlanovDev\MaxMessengerBot\Models\User;
9+
10+
/**
11+
* You will receive this update when the bot has been removed from a chat.
12+
*/
13+
final readonly class BotRemovedFromChatUpdate extends AbstractUpdate
14+
{
15+
/**
16+
* @param int $timestamp Unix-time when the event has occurred.
17+
* @param int $chatId Chat identifier the bot was removed from.
18+
* @param User $user User who removed the bot from the chat.
19+
* @param bool $isChannel Indicates whether the bot has been removed from a channel or not.
20+
*/
21+
public function __construct(
22+
int $timestamp,
23+
public int $chatId,
24+
public User $user,
25+
public bool $isChannel,
26+
) {
27+
parent::__construct(UpdateType::BotRemoved, $timestamp);
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BushlanovDev\MaxMessengerBot\Models\Updates;
6+
7+
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
8+
use BushlanovDev\MaxMessengerBot\Models\User;
9+
10+
/**
11+
* Bot gets this type of update as soon as the title has been changed in a chat.
12+
*/
13+
final readonly class ChatTitleChangedUpdate extends AbstractUpdate
14+
{
15+
/**
16+
* @param int $timestamp Unix-time when the event has occurred.
17+
* @param int $chatId Chat identifier where the event has occurred.
18+
* @param User $user User who changed the title.
19+
* @param string $title The new title.
20+
*/
21+
public function __construct(
22+
int $timestamp,
23+
public int $chatId,
24+
public User $user,
25+
public string $title,
26+
) {
27+
parent::__construct(UpdateType::ChatTitleChanged, $timestamp);
28+
}
29+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BushlanovDev\MaxMessengerBot\Models\Updates;
6+
7+
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
8+
use BushlanovDev\MaxMessengerBot\Models\Chat;
9+
10+
/**
11+
* Bot will get this update when a chat has been created as soon as
12+
* the first user clicked a `chat` button.
13+
*/
14+
final readonly class MessageChatCreatedUpdate extends AbstractUpdate
15+
{
16+
/**
17+
* @param int $timestamp Unix-time when the event has occurred.
18+
* @param Chat $chat The created chat.
19+
* @param string $messageId Message identifier where the button has been clicked.
20+
* @param string|null $startPayload Payload from the chat button.
21+
*/
22+
public function __construct(
23+
int $timestamp,
24+
public Chat $chat,
25+
public string $messageId,
26+
public ?string $startPayload,
27+
) {
28+
parent::__construct(UpdateType::MessageChatCreated, $timestamp);
29+
}
30+
}

0 commit comments

Comments
 (0)