mirror of
https://github.com/BushlanovDev/max-bot-api-client-php.git
synced 2026-10-06 20:10:31 +00:00
Sync Laravel library
This commit is contained in:
+48
-3
@@ -573,6 +573,15 @@ MaxBot::sendUserMessage(123456, 'Hello from Laravel!');
|
||||
|
||||
// Получение обновления
|
||||
$updates = MaxBot::getUpdates();
|
||||
|
||||
// Работа с комментариями к постам в каналах
|
||||
MaxBot::sendComment('mid.post', 'Первый комментарий!');
|
||||
$comments = MaxBot::getComments('mid.post');
|
||||
|
||||
// Установка команд бота (замена депрекированного editBotInfo)
|
||||
MaxBot::editBotCommands([
|
||||
new BotCommand('start', 'Запустить бота'),
|
||||
]);
|
||||
```
|
||||
|
||||
### Artisan команды
|
||||
@@ -625,6 +634,22 @@ class WebhookController extends Controller
|
||||
// ...
|
||||
});
|
||||
|
||||
// Обработчики комментариев к постам в каналах
|
||||
$botManager->onCommentCreated(function (CommentCreatedUpdate $update) {
|
||||
// ...
|
||||
});
|
||||
$botManager->onCommentEdited(function (CommentEditedUpdate $update) {
|
||||
// ...
|
||||
});
|
||||
$botManager->onCommentRemoved(function (CommentRemovedUpdate $update) {
|
||||
// ...
|
||||
});
|
||||
|
||||
// Обработчик изменения прав бота-администратора
|
||||
$botManager->onBotAdminPermissionsChanged(function (BotAdminPermissionsChangedUpdate $update) {
|
||||
// ...
|
||||
});
|
||||
|
||||
// Using Laravel container bindings
|
||||
$botManager->onMessageCreated(MessageHandler::class);
|
||||
$botManager->onCommand('help', HelpCommandHandler::class);
|
||||
@@ -634,15 +659,35 @@ class WebhookController extends Controller
|
||||
}
|
||||
```
|
||||
|
||||
Добавьте маршрут в `routes/web.php`:
|
||||
Добавьте маршрут в `routes/web.php`. Обязательно исключите его из CSRF-проверки —
|
||||
MAX не отправляет CSRF-токен, поэтому без исключения каждый запрос вебхука будет
|
||||
отклонён с ошибкой 419:
|
||||
|
||||
```php
|
||||
Route::post('/bot/webhook', [WebhookController::class, 'handle']);
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
|
||||
Route::post('/bot/webhook', [WebhookController::class, 'handle'])
|
||||
->withoutMiddleware(VerifyCsrfToken::class);
|
||||
```
|
||||
|
||||
Либо разместите маршрут в `routes/api.php` — там CSRF-middleware не применяется.
|
||||
|
||||
### Long Polling
|
||||
|
||||
Создайте artisan команду для получения long polling обновлений:
|
||||
Пакет поставляет готовую artisan-команду для long polling:
|
||||
|
||||
```bash
|
||||
# Запуск с настройками по умолчанию (timeout из maxbot.polling.timeout или 90)
|
||||
php artisan maxbot:polling:start
|
||||
|
||||
# С указанием таймаута
|
||||
php artisan maxbot:polling:start --timeout=30
|
||||
|
||||
# Только определённые типы обновлений
|
||||
php artisan maxbot:polling:start --types=message_created --types=message_callback
|
||||
```
|
||||
|
||||
При необходимости напишите свою команду:
|
||||
|
||||
```php
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ class Api
|
||||
{
|
||||
public const string API_BASE_URL = 'https://platform-api2.max.ru';
|
||||
|
||||
public const string LIBRARY_VERSION = '1.6.6';
|
||||
public const string LIBRARY_VERSION = '1.7.0';
|
||||
|
||||
public const string API_VERSION = '1.2.5';
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Laravel\Commands;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\MaxBotManager;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Config\Repository as Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
@@ -20,7 +22,8 @@ class PollingStartCommand extends Command
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'maxbot:polling:start
|
||||
{--timeout=90 : Timeout in seconds for long polling}';
|
||||
{--timeout= : Timeout in seconds for long polling (0-90, defaults to maxbot.polling.timeout or 90)}
|
||||
{--types=* : Update types to process (optional, e.g. --types=message_created --types=message_callback)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -32,14 +35,33 @@ class PollingStartCommand extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(MaxBotManager $botManager): int
|
||||
public function handle(MaxBotManager $botManager, Config $config): int
|
||||
{
|
||||
$timeout = (int)$this->option('timeout');
|
||||
$timeout = (int)($this->option('timeout') ?? $config->get('maxbot.polling.timeout', 90));
|
||||
|
||||
$updateTypes = null;
|
||||
$types = $this->option('types');
|
||||
if (is_array($types) && !empty($types)) {
|
||||
$updateTypes = [];
|
||||
foreach ($types as $type) {
|
||||
try {
|
||||
$updateTypes[] = UpdateType::from($type);
|
||||
} catch (\ValueError $e) {
|
||||
$this->error("Invalid update type: $type");
|
||||
$this->line('Valid types: ' . implode(', ', array_map(
|
||||
static fn(UpdateType $t) => $t->value,
|
||||
UpdateType::cases(),
|
||||
)));
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->info("Starting long polling with a timeout of $timeout seconds... Press Ctrl+C to stop.");
|
||||
|
||||
try {
|
||||
$botManager->startLongPolling($timeout);
|
||||
$botManager->startLongPolling($timeout, null, $updateTypes);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
// This part is unreachable as startLongPolling is an infinite loop
|
||||
|
||||
@@ -56,6 +56,10 @@ class WebhookSubscribeCommand extends Command
|
||||
$updateTypes[] = UpdateType::from($type);
|
||||
} catch (\ValueError $e) {
|
||||
$this->error("Invalid update type: $type");
|
||||
$this->line('Valid types: ' . implode(', ', array_map(
|
||||
static fn(UpdateType $t) => $t->value,
|
||||
UpdateType::cases(),
|
||||
)));
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ use BushlanovDev\MaxMessengerBot\Enums\SenderAction;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UploadType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\AbstractAttachmentRequest;
|
||||
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;
|
||||
@@ -18,6 +20,7 @@ use BushlanovDev\MaxMessengerBot\Models\ChatList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatMember;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatMembersList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\ChatPatch;
|
||||
use BushlanovDev\MaxMessengerBot\Models\CommentMessage;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\MessageLink;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
@@ -41,6 +44,7 @@ use Illuminate\Support\Facades\Facade;
|
||||
* @method static LongPollingHandler createLongPollingHandler()
|
||||
* @method static UpdateList getUpdates(?int $limit = null, ?int $timeout = null, ?int $marker = null, ?array<UpdateType> $types = null)
|
||||
* @method static BotInfo getBotInfo()
|
||||
* @method static BotCommandsInfo editBotCommands(array<BotCommand> $commands)
|
||||
* @method static Subscription[] getSubscriptions()
|
||||
* @method static Result subscribe(string $url, ?string $secret = null, ?array<UpdateType> $updateTypes = null)
|
||||
* @method static Result unsubscribe(string $url)
|
||||
@@ -48,11 +52,12 @@ use Illuminate\Support\Facades\Facade;
|
||||
* @method static Message sendUserMessage(?int $userId = null, ?string $text = null, ?array<AbstractAttachmentRequest> $attachments = null, ?MessageFormat $format = null, ?MessageLink $link = null, bool $notify = true, bool $disableLinkPreview = false)
|
||||
* @method static Message sendChatMessage(?int $chatId = null, ?string $text = null, ?array<AbstractAttachmentRequest> $attachments = null, ?MessageFormat $format = null, ?MessageLink $link = null, bool $notify = true, bool $disableLinkPreview = false)
|
||||
* @method static UploadEndpoint getUploadUrl(UploadType $type)
|
||||
* @method static string uploadFile(string $uploadUrl, mixed $fileHandle, string $fileName)
|
||||
* @method static AbstractAttachmentRequest uploadAttachment(UploadType $type, string $filePath)
|
||||
* @method static Chat getChat(int $chatId)
|
||||
* @method static Chat getChatByLink(string $chatLink)
|
||||
* @method static ChatList getChats(?int $count = null, ?int $marker = null)
|
||||
* @method static Result deleteChat(int $chatId)
|
||||
* @method static Chat getChatByLink(string $chatLink) Deprecated: `GET /chats/{chatLink}` is not in the Bot API docs and schema since 0.0.33.
|
||||
* @method static ChatList getChats(?int $count = null, ?int $marker = null) Deprecated: the API does not support `GET /chats` since June 2026; collect chat ids from bot_added and bot_started updates instead.
|
||||
* @method static Result deleteChat(int $chatId) Deprecated: `DELETE /chats/{chatId}` is not in the Bot API docs and schema since 0.0.33.
|
||||
* @method static Result sendAction(int $chatId, SenderAction $action)
|
||||
* @method static Message|null getPinnedMessage(int $chatId)
|
||||
* @method static Result unpinMessage(int $chatId)
|
||||
@@ -61,16 +66,21 @@ use Illuminate\Support\Facades\Facade;
|
||||
* @method static Message[] getMessages(int $chatId, ?array<string> $messageIds = null, ?int $from = null, ?int $to = null, ?int $count = null)
|
||||
* @method static Result deleteMessage(string $messageId)
|
||||
* @method static Message getMessageById(string $messageId)
|
||||
* @method static CommentMessage[] getComments(string $messageId, ?array<string> $commentIds = null, ?int $before = null, ?int $after = null, ?int $count = null)
|
||||
* @method static CommentMessage getCommentById(string $messageId, string $commentId)
|
||||
* @method static CommentMessage sendComment(string $messageId, ?string $text = null, ?MessageFormat $format = null, ?MessageLink $link = null, bool $disableLinkPreview = false)
|
||||
* @method static Result editComment(string $messageId, string $commentId, ?string $text = null, ?MessageFormat $format = null, ?MessageLink $link = null)
|
||||
* @method static Result deleteComment(string $messageId, string $commentId)
|
||||
* @method static Result pinMessage(int $chatId, string $messageId, bool $notify = true)
|
||||
* @method static ChatMembersList getAdmins(int $chatId)
|
||||
* @method static ChatMembersList getMembers(int $chatId, ?array<int> $userIds = null, ?int $marker = null, ?int $count = null)
|
||||
* @method static Result deleteAdmin(int $chatId, int $userId)
|
||||
* @method static Result deleteMember(int $chatId, int $userId, bool $block = false)
|
||||
* @method static Result addAdmins(int $chatId, array<ChatAdmin> $admins)
|
||||
* @method static Result addMembers(int $chatId, array<int> $userIds)
|
||||
* @method static Result answerOnCallback(string $callbackId, ?string $notification = null, ?string $text = null, ?array<AbstractAttachmentRequest> $attachments = null, ?MessageLink $link = null, ?MessageFormat $format = null, bool $notify = true)
|
||||
* @method static Result addMembers(int $chatId, array<int> $userIds) Deprecated: `POST /chats/{chatId}/members` was limited since 9 September 2026 and removed on 30 September 2026; the API offers no replacement.
|
||||
* @method static Result answerOnCallback(string $callbackId, ?string $notification = null, ?string $text = null, ?array<AbstractAttachmentRequest> $attachments = null, ?MessageLink $link = null, ?MessageFormat $format = null, bool $notify = true, bool $disableLinkPreview = false)
|
||||
* @method static Result editMessage(string $messageId, ?string $text = null, ?array<AbstractAttachmentRequest> $attachments = null, ?MessageFormat $format = null, ?MessageLink $link = null, bool $notify = true)
|
||||
* @method static BotInfo editBotInfo(BotPatch $botPatch)
|
||||
* @method static BotInfo editBotInfo(BotPatch $botPatch) Deprecated: the API no longer accepts `PATCH /me`; set commands with editBotCommands() and edit name, description and photo on the MAX partner platform.
|
||||
* @method static Chat editChat(int $chatId, ChatPatch $chatPatch)
|
||||
* @method static VideoAttachmentDetails getVideoAttachmentDetails(string $videoToken)
|
||||
*
|
||||
|
||||
@@ -112,15 +112,19 @@ readonly class MaxBotManager
|
||||
* This method should be called from a Laravel command or job.
|
||||
* It will run indefinitely until stopped.
|
||||
*
|
||||
* @param int $timeout
|
||||
* @param int|null $marker
|
||||
* @param UpdateType[]|null $types Update types to fetch, null for all.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function startLongPolling(int $timeout = 90, ?int $marker = null): void
|
||||
public function startLongPolling(int $timeout = 90, ?int $marker = null, ?array $types = null): void
|
||||
{
|
||||
/** @var LongPollingHandler $longPolling */
|
||||
$longPolling = $this->container->make(LongPollingHandler::class);
|
||||
|
||||
$longPolling->handle($timeout, $marker);
|
||||
$longPolling->handle($timeout, $marker, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,6 +52,20 @@ return [
|
||||
'connect_timeout' => (int)env('MAXBOT_CONNECT_TIMEOUT', 5),
|
||||
'read_timeout' => (int)env('MAXBOT_READ_TIMEOUT', 10),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Long Polling Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Timeout in seconds for the long polling getUpdates call (0-90).
|
||||
| Used by the maxbot:polling:start command when no --timeout option
|
||||
| is passed.
|
||||
|
|
||||
*/
|
||||
'polling' => [
|
||||
'timeout' => (int)env('MAXBOT_POLLING_TIMEOUT', 90),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logging Configuration
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\NetworkException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
@@ -33,12 +34,13 @@ final readonly class LongPollingHandler
|
||||
*
|
||||
* @param int $timeout Timeout for the getUpdates call.
|
||||
* @param int|null $marker The marker for which updates to fetch.
|
||||
* @param UpdateType[]|null $types Update types to fetch, null for all.
|
||||
* @return int|null The new marker to be used for the next iteration.
|
||||
* @throws \Exception Re-throws exceptions from the API or dispatcher.
|
||||
*/
|
||||
public function processUpdates(int $timeout, ?int $marker): ?int
|
||||
public function processUpdates(int $timeout, ?int $marker, ?array $types = null): ?int
|
||||
{
|
||||
$updateList = $this->api->getUpdates(timeout: $timeout, marker: $marker);
|
||||
$updateList = $this->api->getUpdates(timeout: $timeout, marker: $marker, types: $types);
|
||||
|
||||
foreach ($updateList->updates as $update) {
|
||||
try {
|
||||
@@ -60,14 +62,15 @@ final readonly class LongPollingHandler
|
||||
*
|
||||
* @param int $timeout Timeout in seconds for long polling (0-90).
|
||||
* @param int|null $marker Initial marker. Pass `null` to get updates you didn't get yet.
|
||||
* @param UpdateType[]|null $types Update types to fetch, null for all.
|
||||
*/
|
||||
public function handle(int $timeout = 90, ?int $marker = null): void
|
||||
public function handle(int $timeout = 90, ?int $marker = null, ?array $types = null): void
|
||||
{
|
||||
$this->listenSignals();
|
||||
// @phpstan-ignore-next-line
|
||||
while (true) {
|
||||
try {
|
||||
$marker = $this->processUpdates($timeout, $marker);
|
||||
$marker = $this->processUpdates($timeout, $marker, $types);
|
||||
} catch (NetworkException $e) {
|
||||
$this->logger->error(
|
||||
'Long-polling network error: {message}',
|
||||
|
||||
@@ -4,8 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Tests\Laravel\Commands;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\Commands\PollingStartCommand;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\MaxBotManager;
|
||||
use Illuminate\Contracts\Config\Repository as Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
@@ -21,6 +23,7 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
final class PollingStartCommandTest extends TestCase
|
||||
{
|
||||
private MockObject&MaxBotManager $botManagerMock;
|
||||
private MockObject&Config $configMock;
|
||||
private PollingStartCommand $command;
|
||||
|
||||
protected function setUp(): void
|
||||
@@ -31,6 +34,12 @@ final class PollingStartCommandTest extends TestCase
|
||||
$this->container->instance(MaxBotManager::class, $this->botManagerMock);
|
||||
$this->container->alias(MaxBotManager::class, 'maxbot.manager');
|
||||
|
||||
$this->configMock = $this->createMock(Config::class);
|
||||
$this->configMock->method('get')->willReturnCallback(
|
||||
static fn(string $key, mixed $default = null) => $key === 'maxbot.polling.timeout' ? 90 : $default,
|
||||
);
|
||||
$this->container->instance(Config::class, $this->configMock);
|
||||
|
||||
$this->command = new PollingStartCommand();
|
||||
$this->command->setLaravel($this->container);
|
||||
|
||||
@@ -48,7 +57,7 @@ final class PollingStartCommandTest extends TestCase
|
||||
$this->botManagerMock
|
||||
->expects($this->once())
|
||||
->method('startLongPolling')
|
||||
->with($timeout);
|
||||
->with($timeout, null, null);
|
||||
|
||||
$this->tester->execute(['--timeout' => $timeout]);
|
||||
$this->tester->assertCommandIsSuccessful();
|
||||
@@ -65,7 +74,7 @@ final class PollingStartCommandTest extends TestCase
|
||||
$this->botManagerMock
|
||||
->expects($this->once())
|
||||
->method('startLongPolling')
|
||||
->with($defaultTimeout);
|
||||
->with($defaultTimeout, null, null);
|
||||
|
||||
$this->tester->execute([]);
|
||||
$this->tester->assertCommandIsSuccessful();
|
||||
@@ -77,6 +86,18 @@ final class PollingStartCommandTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleFailsOnInvalidUpdateType(): void
|
||||
{
|
||||
$statusCode = $this->tester->execute(['--types' => ['not_a_real_type']]);
|
||||
|
||||
$this->assertSame(1, $statusCode, 'Command should return a failure exit code.');
|
||||
|
||||
$output = $this->tester->getDisplay();
|
||||
$this->assertStringContainsString('Invalid update type: not_a_real_type', $output);
|
||||
$this->assertStringContainsString('Valid types: ', $output);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function handleCatchesExceptionAndLogsError(): void
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user