Laravel support

This commit is contained in:
Alex
2025-08-10 15:55:15 +03:00
parent 59a843609b
commit 3b242c1186
10 changed files with 1022 additions and 4 deletions
+17 -3
View File
@@ -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
View File
@@ -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;
}
}
}
+91
View File
@@ -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';
}
}
+347
View File
@@ -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;
}
}
+190
View File
@@ -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',
];
}
}
+67
View File
@@ -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'),
],
];