mirror of
https://github.com/BushlanovDev/max-bot-api-client-php.git
synced 2026-08-17 13:36:16 +00:00
Laravel support
This commit is contained in:
+17
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "bushlanov-dev/max-bot-api-client-php",
|
||||
"description": "Max Bot API Client library",
|
||||
"keywords": ["max messenger", "bot", "max", "api"],
|
||||
"keywords": ["max messenger", "bot", "max", "api", "max bot", "laravel", "laravel max bot"],
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
@@ -17,15 +17,19 @@
|
||||
"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"
|
||||
"psr/http-message": "^1.0||^2.0",
|
||||
"psr/log": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.77",
|
||||
"illuminate/console": "^12.22",
|
||||
"illuminate/http": "^12.22",
|
||||
"illuminate/testing": "^11.0||^12.0",
|
||||
"jaschilz/php-coverage-badger": "^2.0",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"mockery/mockery": "^1.6",
|
||||
"php-mock/php-mock-phpunit": "^2.13",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^12.0",
|
||||
@@ -44,6 +48,16 @@
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"BushlanovDev\\MaxMessengerBot\\Laravel\\MaxBotServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"MaxBot": "BushlanovDev\\MaxMessengerBot\\Laravel\\MaxBotFacade"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"analyse": "vendor/bin/phpstan analyse -c phpstan.neon --memory-limit=256M",
|
||||
"format": "vendor/bin/php-cs-fixer fix --allow-risky=yes src",
|
||||
|
||||
+1
-1
@@ -843,7 +843,7 @@ class Api
|
||||
int $chatId,
|
||||
?array $userIds = null,
|
||||
?int $marker = null,
|
||||
?int $count = null
|
||||
?int $count = null,
|
||||
): ChatMembersList {
|
||||
$query = [
|
||||
'user_ids' => $userIds !== null ? implode(',', $userIds) : null,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Laravel\Commands;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\MaxBotManager;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Artisan command to start processing updates via long polling.
|
||||
*/
|
||||
class PollingStartCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'maxbot:polling:start
|
||||
{--timeout=90 : Timeout in seconds for long polling}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Start the bot to process updates via long polling';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(MaxBotManager $botManager): int
|
||||
{
|
||||
$timeout = (int)$this->option('timeout');
|
||||
|
||||
$this->info("Starting long polling with a timeout of $timeout seconds... Press Ctrl+C to stop.");
|
||||
|
||||
try {
|
||||
$botManager->startLongPolling($timeout);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
// This part is unreachable as startLongPolling is an infinite loop
|
||||
return self::SUCCESS;
|
||||
// @codeCoverageIgnoreEnd
|
||||
} catch (Throwable $e) {
|
||||
Log::error("Long polling failed to start or crashed: {$e->getMessage()}", [
|
||||
'exception' => $e,
|
||||
]);
|
||||
$this->error("❌ Long polling failed: {$e->getMessage()}");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Laravel\Commands;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Artisan command for listing active webhook subscriptions.
|
||||
*/
|
||||
class WebhookListCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'maxbot:webhook:list';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'List all active webhook subscriptions';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(Api $api): int
|
||||
{
|
||||
$this->info('Fetching webhook subscriptions...');
|
||||
|
||||
try {
|
||||
$subscriptions = $api->getSubscriptions();
|
||||
|
||||
if (empty($subscriptions)) {
|
||||
$this->info('No active webhook subscriptions found.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info('Found ' . count($subscriptions) . ' active webhook subscription(s):');
|
||||
$this->newLine();
|
||||
|
||||
$headers = ['URL', 'Update Types', 'Created At'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($subscriptions as $subscription) {
|
||||
$rows[] = [
|
||||
$subscription->url,
|
||||
implode(', ', $subscription->updateTypes ? array_map(fn (UpdateType $updateType) => $updateType->value, $subscription->updateTypes) : ['all']),
|
||||
date('Y-m-d H:i:s', $subscription->time),
|
||||
];
|
||||
}
|
||||
|
||||
$this->table($headers, $rows);
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (Throwable $e) {
|
||||
Log::error("Webhook list error: {$e->getMessage()}", [
|
||||
'exception' => $e,
|
||||
]);
|
||||
$this->error("❌ Webhook list error: {$e->getMessage()}");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Laravel\Commands;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Config\Repository as Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Artisan command for subscribing to webhook updates.
|
||||
*/
|
||||
class WebhookSubscribeCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'maxbot:webhook:subscribe
|
||||
{url : The webhook URL to subscribe to}
|
||||
{--secret= : Secret key for webhook verification (optional)}
|
||||
{--types=* : Update types to subscribe to (optional)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Subscribe bot to webhook updates';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(Api $api, Config $config): int
|
||||
{
|
||||
$url = (string)$this->argument('url'); // @phpstan-ignore-line
|
||||
$secret = $this->option('secret') ?? $config->get('maxbot.webhook_secret');
|
||||
$types = $this->option('types');
|
||||
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$this->error('Invalid URL provided.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$updateTypes = null;
|
||||
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");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Subscribing to webhook...');
|
||||
$this->line("URL: $url");
|
||||
if ($secret) {
|
||||
$this->line("Secret: " . str_repeat('*', strlen($secret)));
|
||||
}
|
||||
if ($updateTypes) {
|
||||
$this->line("Update types: " . implode(', ', array_map(fn($type) => $type->value, $updateTypes)));
|
||||
} else {
|
||||
$this->line("Update types: All (default)");
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $api->subscribe($url, $secret, $updateTypes);
|
||||
|
||||
if ($result->success) {
|
||||
$this->info('✅ Successfully subscribed to webhook!');
|
||||
|
||||
return self::SUCCESS;
|
||||
} else {
|
||||
$this->error('❌ Failed to subscribe to webhook.');
|
||||
$this->line("Response: $result->message");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
Log::error("Webhook subscription error: {$e->getMessage()}", [
|
||||
'exception' => $e,
|
||||
]);
|
||||
$this->error("❌ Webhook subscription error: {$e->getMessage()}");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Laravel\Commands;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Artisan command for unsubscribing from webhook updates.
|
||||
*/
|
||||
class WebhookUnsubscribeCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'maxbot:webhook:unsubscribe
|
||||
{url : The webhook URL to unsubscribe from}
|
||||
{--confirm : Skip confirmation prompt}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Unsubscribe bot from webhook updates';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(Api $api): int
|
||||
{
|
||||
$url = (string)$this->argument('url'); // @phpstan-ignore-line
|
||||
$confirm = $this->option('confirm');
|
||||
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$this->error('Invalid URL provided.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if (!$confirm) {
|
||||
if (!$this->confirm("Are you sure you want to unsubscribe from webhook URL: $url?")) {
|
||||
$this->info('Operation cancelled.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Unsubscribing from webhook...');
|
||||
$this->line("URL: $url");
|
||||
|
||||
try {
|
||||
$result = $api->unsubscribe($url);
|
||||
|
||||
if ($result->success) {
|
||||
$this->info('✅ Successfully unsubscribed from webhook!');
|
||||
|
||||
return self::SUCCESS;
|
||||
} else {
|
||||
$this->error('❌ Failed to unsubscribe from webhook.');
|
||||
$this->line("Response: $result->message");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
Log::error("Webhook unsubscribe error: {$e->getMessage()}", [
|
||||
'exception' => $e,
|
||||
]);
|
||||
$this->error("❌ Webhook unsubscribe error: {$e->getMessage()}");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Laravel;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MessageFormat;
|
||||
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\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;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use BushlanovDev\MaxMessengerBot\Models\VideoAttachmentDetails;
|
||||
use BushlanovDev\MaxMessengerBot\UpdateDispatcher;
|
||||
use BushlanovDev\MaxMessengerBot\WebhookHandler;
|
||||
use BushlanovDev\MaxMessengerBot\LongPollingHandler;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
/**
|
||||
* Laravel Facade for Max Bot API Client.
|
||||
*
|
||||
* Provides static access to the Max Bot API methods through Laravel's facade system.
|
||||
*
|
||||
* @method static array<string, mixed> request(string $method, string $uri, array<string, mixed> $queryParams = [], array<string, mixed> $body = [])
|
||||
* @method static UpdateDispatcher getUpdateDispatcher()
|
||||
* @method static WebhookHandler createWebhookHandler(?string $secret = null)
|
||||
* @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 Subscription[] getSubscriptions()
|
||||
* @method static Result subscribe(string $url, ?string $secret = null, ?array<UpdateType> $updateTypes = null)
|
||||
* @method static Result unsubscribe(string $url)
|
||||
* @method static Message sendMessage(?int $userId = null, ?int $chatId = null, ?string $text = null, ?array<AbstractAttachmentRequest> $attachments = null, ?MessageFormat $format = null, ?MessageLink $link = null, bool $notify = true, bool $disableLinkPreview = false)
|
||||
* @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 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 Result sendAction(int $chatId, SenderAction $action)
|
||||
* @method static Message|null getPinnedMessage(int $chatId)
|
||||
* @method static Result unpinMessage(int $chatId)
|
||||
* @method static ChatMember getMembership(int $chatId)
|
||||
* @method static Result leaveChat(int $chatId)
|
||||
* @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 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 deleteAdmins(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 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 Chat editChat(int $chatId, ChatPatch $chatPatch)
|
||||
* @method static VideoAttachmentDetails getVideoAttachmentDetails(string $videoToken)
|
||||
*
|
||||
* @see Api
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class MaxBotFacade extends Facade
|
||||
{
|
||||
/**
|
||||
* Get the registered name of the component.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'maxbot';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Laravel;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\SecurityException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\SerializationException;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
|
||||
use BushlanovDev\MaxMessengerBot\UpdateDispatcher;
|
||||
use BushlanovDev\MaxMessengerBot\WebhookHandler;
|
||||
use BushlanovDev\MaxMessengerBot\LongPollingHandler;
|
||||
use GuzzleHttp\Psr7\ServerRequest;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Max Bot Manager for Laravel integration.
|
||||
*
|
||||
* Provides convenient methods for integrating Max Bot with Laravel applications.
|
||||
* Handles webhook processing, long polling, and event dispatching within Laravel context.
|
||||
*/
|
||||
class MaxBotManager
|
||||
{
|
||||
/**
|
||||
* @param Container $container
|
||||
* @param Api $api
|
||||
* @param UpdateDispatcher $dispatcher
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Container $container,
|
||||
private readonly Api $api,
|
||||
private readonly UpdateDispatcher $dispatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle webhook request in Laravel controller.
|
||||
*
|
||||
* Example usage in a controller:
|
||||
* ```php
|
||||
* public function webhook(Request $request, MaxBotManager $botManager)
|
||||
* {
|
||||
* return $botManager->handleWebhook($request);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public function handleWebhook(Request $request): JsonResponse|Response
|
||||
{
|
||||
try {
|
||||
/** @var WebhookHandler $webhookHandler */
|
||||
$webhookHandler = $this->container->make(WebhookHandler::class);
|
||||
|
||||
$headers = array_map(function ($values) {
|
||||
return array_filter($values, fn($value) => $value !== null);
|
||||
}, $request->headers->all());
|
||||
|
||||
$webhookHandler->handle(
|
||||
new ServerRequest(
|
||||
$request->getMethod(),
|
||||
$request->getUri(),
|
||||
$headers,
|
||||
$request->getContent(),
|
||||
$request->getProtocolVersion() ?? '1.1',
|
||||
)
|
||||
);
|
||||
|
||||
return new Response('', 200);
|
||||
} catch (SecurityException $e) {
|
||||
Log::warning("Webhook security error: {$e->getMessage()}", [
|
||||
'exception' => $e,
|
||||
'headers' => $request->headers->all(),
|
||||
]);
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Forbidden',
|
||||
], 403);
|
||||
} catch (SerializationException $e) {
|
||||
Log::error("Webhook serialization error: {$e->getMessage()}", [
|
||||
'exception' => $e,
|
||||
'request_content' => $request->getContent(),
|
||||
]);
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Bad Request',
|
||||
], 400);
|
||||
} catch (Throwable $e) {
|
||||
Log::error("Webhook processing error: {$e->getMessage()}", [
|
||||
'exception' => $e,
|
||||
'request_content' => $request->getContent(),
|
||||
'headers' => $request->headers->all(),
|
||||
]);
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'Internal Server Error',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start long polling in Laravel context.
|
||||
* This method should be called from a Laravel command or job.
|
||||
* It will run indefinitely until stopped.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function startLongPolling(int $timeout = 90, ?int $marker = null): void
|
||||
{
|
||||
/** @var LongPollingHandler $longPolling */
|
||||
$longPolling = $this->container->make(LongPollingHandler::class);
|
||||
|
||||
$longPolling->handle($timeout, $marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a handler for a specific update type.
|
||||
*
|
||||
* @param UpdateType $type The type of update to handle.
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function addHandler(UpdateType $type, callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->addHandler($type, $this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a command handler.
|
||||
*
|
||||
* @param string $command Command name (without slash)
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onCommand(string $command, callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onCommand($command, $this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a message created handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onMessageCreated(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onMessageCreated($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onMessageCallback(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onMessageCallback($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a message edited handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onMessageEdited(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onMessageEdited($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a message removed handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onMessageRemoved(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onMessageRemoved($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a bot added handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onBotAdded(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onBotAdded($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a bot removed handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onBotRemoved(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onBotRemoved($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a user added handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onUserAdded(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onUserAdded($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a user removed handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onUserRemoved(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onUserRemoved($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a bot started handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onBotStarted(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onBotStarted($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a chat title changed handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onChatTitleChanged(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onChatTitleChanged($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a message chat created handler.
|
||||
*
|
||||
* @param callable|string $handler Can be a closure, callable, or Laravel container binding.
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function onMessageChatCreated(callable|string $handler): void
|
||||
{
|
||||
$this->dispatcher->onMessageChatCreated($this->resolveHandler($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API instance.
|
||||
*/
|
||||
public function getApi(): Api
|
||||
{
|
||||
return $this->api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the update dispatcher.
|
||||
*/
|
||||
public function getDispatcher(): UpdateDispatcher
|
||||
{
|
||||
return $this->dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a handler that might be a Laravel container binding.
|
||||
*
|
||||
* @param callable|string $handler
|
||||
* @return callable
|
||||
* @phpstan-return callable(AbstractUpdate, Api): void
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
private function resolveHandler(callable|string $handler): callable
|
||||
{
|
||||
if (is_string($handler)) {
|
||||
if ($this->container->bound($handler)) {
|
||||
$resolved = $this->container->make($handler);
|
||||
|
||||
if (is_callable($resolved)) {
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
if (is_object($resolved) && method_exists($resolved, 'handle')) {
|
||||
/** @var callable */
|
||||
return [$resolved, 'handle'];
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(
|
||||
"Handler class '$handler' is not callable and doesn't have a handle method."
|
||||
);
|
||||
}
|
||||
|
||||
if (str_contains($handler, '@')) {
|
||||
[$class, $method] = explode('@', $handler, 2);
|
||||
$instance = $this->container->make($class);
|
||||
/** @var callable */
|
||||
return [$instance, $method];
|
||||
}
|
||||
|
||||
if (class_exists($handler)) {
|
||||
$instance = $this->container->make($handler);
|
||||
if (method_exists($instance, 'handle')) {
|
||||
/** @var callable */
|
||||
return [$instance, 'handle'];
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Unable to resolve handler: $handler");
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Laravel;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Api;
|
||||
use BushlanovDev\MaxMessengerBot\Client;
|
||||
use BushlanovDev\MaxMessengerBot\ClientApiInterface;
|
||||
use BushlanovDev\MaxMessengerBot\ModelFactory;
|
||||
use BushlanovDev\MaxMessengerBot\UpdateDispatcher;
|
||||
use BushlanovDev\MaxMessengerBot\WebhookHandler;
|
||||
use BushlanovDev\MaxMessengerBot\LongPollingHandler;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\Commands\WebhookSubscribeCommand;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\Commands\WebhookUnsubscribeCommand;
|
||||
use BushlanovDev\MaxMessengerBot\Laravel\Commands\WebhookListCommand;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Contracts\Config\Repository as Config;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Laravel Service Provider for Max Bot API Client.
|
||||
* Registers all necessary services in the Laravel container.
|
||||
*/
|
||||
class MaxBotServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->mergeConfigFrom(
|
||||
__DIR__ . '/config/maxbot.php',
|
||||
'maxbot',
|
||||
);
|
||||
|
||||
$this->app->singleton(ClientApiInterface::class, function (Application $app) {
|
||||
$config = $app->make(Config::class);
|
||||
$accessToken = $config->get('maxbot.access_token');
|
||||
|
||||
if (empty($accessToken)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Max Bot access token is not configured. Please set MAXBOT_ACCESS_TOKEN in your .env file.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!class_exists(\GuzzleHttp\Client::class) || !class_exists(\GuzzleHttp\Psr7\HttpFactory::class)) {
|
||||
throw new \LogicException(
|
||||
'Guzzle HTTP client is required. Please run "composer require guzzlehttp/guzzle".'
|
||||
);
|
||||
}
|
||||
|
||||
$timeout = $config->get('maxbot.timeout', 10);
|
||||
$connectTimeout = $config->get('maxbot.connect_timeout', 5);
|
||||
$readTimeout = $config->get('maxbot.read_timeout', 10);
|
||||
$baseUrl = $config->get('maxbot.base_url', 'https://botapi.max.ru');
|
||||
$apiVersion = $config->get('maxbot.api_version', Api::API_VERSION);
|
||||
|
||||
$guzzle = new \GuzzleHttp\Client([
|
||||
'timeout' => $timeout,
|
||||
'connect_timeout' => $connectTimeout,
|
||||
'read_timeout' => $readTimeout,
|
||||
'headers' => [
|
||||
'User-Agent' => 'max-bot-api-client-php/' . Api::LIBRARY_VERSION
|
||||
. ' Laravel/' . $app->version() . ' PHP/' . PHP_VERSION
|
||||
],
|
||||
]);
|
||||
|
||||
$httpFactory = new \GuzzleHttp\Psr7\HttpFactory();
|
||||
|
||||
return new Client(
|
||||
$accessToken,
|
||||
$guzzle,
|
||||
$httpFactory,
|
||||
$httpFactory,
|
||||
$baseUrl,
|
||||
$apiVersion,
|
||||
$app->make(LoggerInterface::class),
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->singleton(ModelFactory::class, function () {
|
||||
return new ModelFactory();
|
||||
});
|
||||
|
||||
$this->app->singleton(UpdateDispatcher::class, function (Application $app) {
|
||||
return new UpdateDispatcher($app->make(Api::class));
|
||||
});
|
||||
|
||||
$this->app->singleton(Api::class, function (Application $app) {
|
||||
$config = $app->make(Config::class);
|
||||
$accessToken = $config->get('maxbot.access_token');
|
||||
|
||||
if (empty($accessToken)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Max Bot access token is not configured. Please set MAXBOT_ACCESS_TOKEN in your .env file.'
|
||||
);
|
||||
}
|
||||
|
||||
return new Api(
|
||||
$accessToken,
|
||||
$app->make(ClientApiInterface::class),
|
||||
$app->make(ModelFactory::class),
|
||||
$app->make(LoggerInterface::class),
|
||||
$app->make(UpdateDispatcher::class),
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind(WebhookHandler::class, function (Application $app) {
|
||||
$config = $app->make(Config::class);
|
||||
$secret = $config->get('maxbot.webhook_secret');
|
||||
|
||||
return new WebhookHandler(
|
||||
$app->make(UpdateDispatcher::class),
|
||||
$app->make(ModelFactory::class),
|
||||
$app->make(LoggerInterface::class),
|
||||
$secret,
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind(LongPollingHandler::class, function (Application $app) {
|
||||
return new LongPollingHandler(
|
||||
$app->make(Api::class),
|
||||
$app->make(UpdateDispatcher::class),
|
||||
$app->make(LoggerInterface::class),
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->singleton(MaxBotManager::class, function (Application $app) {
|
||||
return new MaxBotManager(
|
||||
$app,
|
||||
$app->make(Api::class),
|
||||
$app->make(UpdateDispatcher::class),
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->alias(Api::class, 'maxbot');
|
||||
$this->app->alias(Api::class, 'maxbot.api');
|
||||
$this->app->alias(ClientApiInterface::class, 'maxbot.client');
|
||||
$this->app->alias(UpdateDispatcher::class, 'maxbot.dispatcher');
|
||||
$this->app->alias(WebhookHandler::class, 'maxbot.webhook');
|
||||
$this->app->alias(LongPollingHandler::class, 'maxbot.polling');
|
||||
$this->app->alias(MaxBotManager::class, 'maxbot.manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->publishes([
|
||||
__DIR__ . '/config/maxbot.php' => $this->app->configPath('maxbot.php'),
|
||||
], 'maxbot-config');
|
||||
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->commands([
|
||||
WebhookSubscribeCommand::class,
|
||||
WebhookUnsubscribeCommand::class,
|
||||
WebhookListCommand::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function provides(): array
|
||||
{
|
||||
return [
|
||||
Api::class,
|
||||
ClientApiInterface::class,
|
||||
ModelFactory::class,
|
||||
UpdateDispatcher::class,
|
||||
WebhookHandler::class,
|
||||
LongPollingHandler::class,
|
||||
MaxBotManager::class,
|
||||
'maxbot',
|
||||
'maxbot.api',
|
||||
'maxbot.client',
|
||||
'maxbot.dispatcher',
|
||||
'maxbot.webhook',
|
||||
'maxbot.polling',
|
||||
'maxbot.manager',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Max Bot Access Token
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Your bot's access token from @MasterBot. This token is required for
|
||||
| authentication with the Max Bot API. You can obtain it by creating
|
||||
| a bot through @MasterBot in the Max messenger.
|
||||
|
|
||||
*/
|
||||
'access_token' => env('MAXBOT_ACCESS_TOKEN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webhook Secret
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Secret key for verifying the authenticity of webhook requests.
|
||||
| This is optional but recommended for security. Set this if you're
|
||||
| using webhooks to receive updates.
|
||||
|
|
||||
*/
|
||||
'webhook_secret' => env('MAXBOT_WEBHOOK_SECRET'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| API Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Configuration for the Max Bot API connection.
|
||||
|
|
||||
*/
|
||||
'base_url' => env('MAXBOT_BASE_URL', 'https://botapi.max.ru'),
|
||||
'api_version' => env('MAXBOT_API_VERSION', '0.0.6'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTP Client Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Configuration for the HTTP client used to communicate with the API.
|
||||
| All values are in seconds.
|
||||
|
|
||||
*/
|
||||
'timeout' => (int)env('MAXBOT_TIMEOUT', 10),
|
||||
'connect_timeout' => (int)env('MAXBOT_CONNECT_TIMEOUT', 5),
|
||||
'read_timeout' => (int)env('MAXBOT_READ_TIMEOUT', 10),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logging Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Whether to enable detailed logging of API requests and responses.
|
||||
| This uses Laravel's configured logger.
|
||||
|
|
||||
*/
|
||||
'logging' => [
|
||||
'enabled' => (bool)env('MAXBOT_LOGGING_ENABLED', false),
|
||||
'level' => env('MAXBOT_LOGGING_LEVEL', 'debug'),
|
||||
],
|
||||
];
|
||||
Reference in New Issue
Block a user