diff --git a/config/telegraph.php b/config/telegraph.php index 0e0a491a7..ca6f73bd4 100644 --- a/config/telegraph.php +++ b/config/telegraph.php @@ -47,19 +47,30 @@ * If enabled, unknown webhook commands are * reported as exception in application logs */ - 'report_unknown_commands' => true, + 'report_unknown_commands' => env('TELEGRAPH_REPORT_UNKNOWN_COMMANDS', true), + + /** + * secret token to be sent in a X-Telegram-Bot-Api-Secret-Token header + * to verify the authenticity of the webhook + */ + 'secret' => env('TELEGRAPH_WEBHOOK_SECRET'), + + /** + * maximum allowed simultaneous connections to the webhook (defaults to 40) + */ + 'max_connections' => env('TELEGRAPH_WEBHOOK_MAX_CONNECTIONS', 40), /* * If enabled, Telegraph dumps received * webhook messages to logs */ - 'debug' => false, + 'debug' => env('TELEGRAPH_WEBHOOK_DEBUG', false), ], /* * Sets HTTP request timeout when interacting with Telegram servers */ - 'http_timeout' => 30, + 'http_timeout' => env('TELEGRAPH_HTTP_TIMEOUT', 30), 'security' => [ /* diff --git a/docs/13.api/1.bots.md b/docs/13.api/1.bots.md index efc4e85ca..77042f10b 100644 --- a/docs/13.api/1.bots.md +++ b/docs/13.api/1.bots.md @@ -82,6 +82,12 @@ register a webhook for the active bot Telegraph::registerWebhook()->send(); ``` +you can use the method parameters to customize the webhook settings: + +- `dropPendingUpdates`: drops pending updates from telegram +- `maxConnections`: maximum allowed simultaneous connections to the webhook (defaults to 40) +- `secretToken`: secret token to be sent in a `X-Telegram-Bot-Api-Secret-Token` header to verify the authenticity of the webhook + ## `unregisterWebhook()` unregister a webhook for the active bot diff --git a/docs/14.models/1.telegraph-bot.md b/docs/14.models/1.telegraph-bot.md index b5beaaf5b..c96b1b836 100644 --- a/docs/14.models/1.telegraph-bot.md +++ b/docs/14.models/1.telegraph-bot.md @@ -97,6 +97,12 @@ register a webhook url $telegraphBot->registerWebhook()->send(); ``` +you can use the method parameters to customize the webhook settings: + +- `dropPendingUpdates`: drops pending updates from telegram +- `maxConnections`: maximum allowed simultaneous connections to the webhook (defaults to 40) +- `secretToken`: secret token to be sent in a `X-Telegram-Bot-Api-Secret-Token` header to verify the authenticity of the webhook + ### `unregisterWebhook()` unregister a webhook url diff --git a/docs/15.webhooks/2.registering-webhooks.md b/docs/15.webhooks/2.registering-webhooks.md index 46c23b7b1..eae3418cc 100644 --- a/docs/15.webhooks/2.registering-webhooks.md +++ b/docs/15.webhooks/2.registering-webhooks.md @@ -13,6 +13,12 @@ You can register a webhook calling the `telegraph:set-webhook` artisan command: php artisan telegraph:set-webhook ``` +some options are available in order to customize the webhook settings, refer to [bots documentation](models/telegraph-bot#registerWebhook) for more info: + +```shell +php artisan telegraph:set-webhook --drop-pending-updates --max-connections=100 --secret=super_secret_token +``` + ### programmatically if you are implementing a custom bot management logic, you can register a webhok using the `TelegraphBot` model: @@ -23,6 +29,8 @@ if you are implementing a custom bot management logic, you can register a webhok $telegraphBot->registerWebhook()->send(); ``` +some options are available in order to customize the webhook settings, refer to [bots documentation](models/telegraph-bot#registerWebhook) for more info: + > [!WARNING] > Manual updates polling is not available if a webhook is set up for the bot. Webhook should be remove first using its [unregisterWebhook](webhooks/deleting-webhooks) method diff --git a/src/Commands/CreateNewBotCommand.php b/src/Commands/CreateNewBotCommand.php index f8f002d87..570ac7bda 100644 --- a/src/Commands/CreateNewBotCommand.php +++ b/src/Commands/CreateNewBotCommand.php @@ -4,10 +4,15 @@ use DefStudio\Telegraph\Models\TelegraphBot; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Artisan; class CreateNewBotCommand extends Command { - public $signature = 'telegraph:new-bot'; + public $signature = 'telegraph:new-bot + {--drop-pending-updates : drops pending updates from telegram} + {--max-connections=40 : maximum allowed simultaneous connections to the webhook (defaults to 40)} + {--secret= : secret token to be sent in a X-Telegram-Bot-Api-Secret-Token header to verify the authenticity of the webhook} + '; public $description = 'Create a new TelegraphBot'; @@ -48,9 +53,13 @@ public function handle(): int } - if ($this->confirm(__('telegraph::commands.new_bot.ask_to_setup_webhook'))) { - $bot->registerWebhook()->send(); + Artisan::call('telegraph:set-webhook', [ + 'bot' => $bot->id, + '--drop-pending-updates' => $this->option('drop-pending-updates'), + '--max-connections' => $this->option('max-connections'), + '--secret' => $this->option('secret'), + ]); } $this->info(__('telegraph::commands.new_bot.bot_created', ['bot_name' => $bot->name])); diff --git a/src/Commands/SetTelegramWebhookCommand.php b/src/Commands/SetTelegramWebhookCommand.php index 7ab813577..bb4d80fc9 100644 --- a/src/Commands/SetTelegramWebhookCommand.php +++ b/src/Commands/SetTelegramWebhookCommand.php @@ -8,7 +8,11 @@ class SetTelegramWebhookCommand extends Command { public $signature = 'telegraph:set-webhook - {bot? : the ID of the bot (if the system contain a single bot, it can be left empty)}'; + {bot? : the ID of the bot (if the system contain a single bot, it can be left empty)} + {--drop-pending-updates : drops pending updates from telegram} + {--max-connections=40 : maximum allowed simultaneous connections to the webhook (defaults to 40)} + {--secret= : secret token to be sent in a X-Telegram-Bot-Api-Secret-Token header to verify the authenticity of the webhook} + '; public $description = 'Set webhook url in telegram bot configuration'; @@ -29,13 +33,18 @@ public function handle(): int return self::FAILURE; } - $telegraph = $bot->registerWebhook(); + $dropPendingUpdates = $this->option('drop-pending-updates'); + $maxConnections = $this->option('max-connections'); + $secret = $this->option('secret'); + + /* @phpstan-ignore-next-line */ + $telegraph = $bot->registerWebhook($dropPendingUpdates, $maxConnections, $secret); $this->info(__('telegraph::commands.set_webhook.sending_setup_request', ['api_url' => $telegraph->getApiUrl()])); $reponse = $telegraph->send(); - $ok = (bool)$reponse->json('ok'); + $ok = (bool) $reponse->json('ok'); if (!$ok) { $this->error(__('telegraph::errors.failed_to_register_webhook')); diff --git a/src/Concerns/InteractsWithWebhooks.php b/src/Concerns/InteractsWithWebhooks.php index 581eedc7c..3e9640b42 100644 --- a/src/Concerns/InteractsWithWebhooks.php +++ b/src/Concerns/InteractsWithWebhooks.php @@ -26,18 +26,21 @@ private function getWebhookUrl(): string return $url; } - return $customWebhookUrl . route('telegraph.webhook', $this->getBot(), false); + return $customWebhookUrl.route('telegraph.webhook', $this->getBot(), false); } - public function registerWebhook(bool $dropPendingUpdates = false): Telegraph + public function registerWebhook(bool $dropPendingUpdates = null, int $maxConnections = null, string $secretToken = null): Telegraph { $telegraph = clone $this; $telegraph->endpoint = self::ENDPOINT_SET_WEBHOOK; - $telegraph->data = [ + $telegraph->data = collect([ 'url' => $this->getWebhookUrl(), 'drop_pending_updates' => $dropPendingUpdates, - ]; + 'max_connections' => $maxConnections ?? config('telegraph.max_connections'), + 'secret_token' => $secretToken ?? config('telegraph.secret_token'), + ])->filter() + ->toArray(); return $telegraph; } diff --git a/src/Facades/Telegraph.php b/src/Facades/Telegraph.php index 3e2bba71d..dcc52c38e 100644 --- a/src/Facades/Telegraph.php +++ b/src/Facades/Telegraph.php @@ -86,9 +86,9 @@ * @method static void assertSentFiles(string $endpoint, array $files = null) * @method static void assertSent(string $message, bool $exact = true) * @method static void assertNothingSent() - * @method static void assertRegisteredWebhook() - * @method static void assertUnregisteredWebhook() - * @method static void assertRequestedWebhookDebugInfo() + * @method static void assertRegisteredWebhook(array $data = null, bool $exact = true) + * @method static void assertUnregisteredWebhook(array $data = null, bool $exact = true) + * @method static void assertRequestedWebhookDebugInfo(array $data = null, bool $exact = true) * @method static void assertRepliedWebhook(string $message) * @method static void assertRepliedWebhookIsAlert() * @method static void assertStoredFile(string $fileId) diff --git a/src/Models/TelegraphBot.php b/src/Models/TelegraphBot.php index 42082c71a..dd266c38c 100644 --- a/src/Models/TelegraphBot.php +++ b/src/Models/TelegraphBot.php @@ -101,9 +101,9 @@ public function chats(): HasMany return $this->hasMany(config('telegraph.models.chat'), 'telegraph_bot_id'); } - public function registerWebhook(): Telegraph + public function registerWebhook(bool $dropPendingUpdates = null, int $maxConnections = null, string $secretToken = null): Telegraph { - return TelegraphFacade::bot($this)->registerWebhook(); + return TelegraphFacade::bot($this)->registerWebhook($dropPendingUpdates, $maxConnections, $secretToken); } public function unregisterWebhook(bool $dropPendingUpdates = false): Telegraph diff --git a/src/Support/Testing/Fakes/TelegraphFake.php b/src/Support/Testing/Fakes/TelegraphFake.php index 2218123b2..bbf198114 100644 --- a/src/Support/Testing/Fakes/TelegraphFake.php +++ b/src/Support/Testing/Fakes/TelegraphFake.php @@ -23,7 +23,7 @@ class TelegraphFake extends Telegraph use FakesRequests; /** - * @param array> $replies + * @param array $replies */ public function __construct(array $replies = []) { @@ -135,19 +135,19 @@ public function assertNothingSent(): void Assert::assertEmpty(self::$sentMessages, sprintf("Failed to assert that no request were sent (sent %d requests so far)", count(self::$sentMessages))); } - public function assertRegisteredWebhook(): void + public function assertRegisteredWebhook(array $data = null, bool $exact = true): void { - $this->assertSentData(Telegraph::ENDPOINT_SET_WEBHOOK); + $this->assertSentData(Telegraph::ENDPOINT_SET_WEBHOOK, $data, $exact); } - public function assertUnregisteredWebhook(): void + public function assertUnregisteredWebhook(array $data = null, bool $exact = true): void { - $this->assertSentData(Telegraph::ENDPOINT_UNSET_WEBHOOK); + $this->assertSentData(Telegraph::ENDPOINT_UNSET_WEBHOOK, $data, $exact); } - public function assertRequestedWebhookDebugInfo(): void + public function assertRequestedWebhookDebugInfo(array $data = null, bool $exact = true): void { - $this->assertSentData(Telegraph::ENDPOINT_GET_WEBHOOK_DEBUG_INFO); + $this->assertSentData(Telegraph::ENDPOINT_GET_WEBHOOK_DEBUG_INFO, $data, $exact); } public function assertRepliedWebhook(string $message): void diff --git a/tests/.pest/snapshots/Unit/Concerns/InteractsWithWebhooksTest/it_can_register_a_webhook_dropping_pending_updates.snap b/tests/.pest/snapshots/Unit/Concerns/InteractsWithWebhooksTest/it_can_register_a_webhook_dropping_pending_updates.snap new file mode 100644 index 000000000..c3a40ebe5 --- /dev/null +++ b/tests/.pest/snapshots/Unit/Concerns/InteractsWithWebhooksTest/it_can_register_a_webhook_dropping_pending_updates.snap @@ -0,0 +1,8 @@ +{ + "url": "https:\/\/api.telegram.org\/bot3f3814e1-5836-3d77-904e-60f64b15df36\/setWebhook", + "payload": { + "url": "https:\/\/testbot.defstudio.dev\/telegraph\/3f3814e1-5836-3d77-904e-60f64b15df36\/webhook", + "drop_pending_updates": true + }, + "files": [] +} \ No newline at end of file diff --git a/tests/.pest/snapshots/Unit/Concerns/InteractsWithWebhooksTest/it_can_register_a_webhook_setting_its_max_connections.snap b/tests/.pest/snapshots/Unit/Concerns/InteractsWithWebhooksTest/it_can_register_a_webhook_setting_its_max_connections.snap new file mode 100644 index 000000000..bd6fd1324 --- /dev/null +++ b/tests/.pest/snapshots/Unit/Concerns/InteractsWithWebhooksTest/it_can_register_a_webhook_setting_its_max_connections.snap @@ -0,0 +1,8 @@ +{ + "url": "https:\/\/api.telegram.org\/bot3f3814e1-5836-3d77-904e-60f64b15df36\/setWebhook", + "payload": { + "url": "https:\/\/testbot.defstudio.dev\/telegraph\/3f3814e1-5836-3d77-904e-60f64b15df36\/webhook", + "max_connections": 42 + }, + "files": [] +} \ No newline at end of file diff --git a/tests/.pest/snapshots/Unit/Concerns/InteractsWithWebhooksTest/it_can_register_a_webhook_setting_its_secret_token.snap b/tests/.pest/snapshots/Unit/Concerns/InteractsWithWebhooksTest/it_can_register_a_webhook_setting_its_secret_token.snap new file mode 100644 index 000000000..bc9702cdf --- /dev/null +++ b/tests/.pest/snapshots/Unit/Concerns/InteractsWithWebhooksTest/it_can_register_a_webhook_setting_its_secret_token.snap @@ -0,0 +1,8 @@ +{ + "url": "https:\/\/api.telegram.org\/bot3f3814e1-5836-3d77-904e-60f64b15df36\/setWebhook", + "payload": { + "url": "https:\/\/testbot.defstudio.dev\/telegraph\/3f3814e1-5836-3d77-904e-60f64b15df36\/webhook", + "secret_token": "super-secret-token" + }, + "files": [] +} \ No newline at end of file diff --git a/tests/Feature/Commands/CreateNewBotCommandTest.php b/tests/Feature/Commands/CreateNewBotCommandTest.php index bdd18a564..262fca06e 100644 --- a/tests/Feature/Commands/CreateNewBotCommandTest.php +++ b/tests/Feature/Commands/CreateNewBotCommandTest.php @@ -121,3 +121,87 @@ Facade::assertRegisteredWebhook(); }); + +it('can register the new bot webhook dropping pending updates', function () { + withfakeUrl(); + + Facade::fake([ + Telegraph::ENDPOINT_SET_WEBHOOK => [ + 'ok' => true, + ], + ]); + + artisan('telegraph:new-bot', ['--drop-pending-updates' => true]) + ->expectsOutput('You are about to create a new Telegram Bot') + ->expectsQuestion("Please, enter the bot token", "123456789") + ->expectsQuestion("Enter the bot name (optional)", "") + ->expectsQuestion("Do you want to add a chat to this bot?", false) + ->expectsQuestion("Do you want to setup a webhook for this bot?", true) + ->assertExitCode(Command::SUCCESS); + + + expect(TelegraphBot::first()) + ->not->toBeNull() + ->token->toBe('123456789') + ->name->toBe('Bot #1'); + + Facade::assertRegisteredWebhook([ + 'drop_pending_updates' => true, + ], false); +}); + +it('can register the new bot webhook settings its max connections', function () { + withfakeUrl(); + + Facade::fake([ + Telegraph::ENDPOINT_SET_WEBHOOK => [ + 'ok' => true, + ], + ]); + + artisan('telegraph:new-bot', ['--max-connections' => 99]) + ->expectsOutput('You are about to create a new Telegram Bot') + ->expectsQuestion("Please, enter the bot token", "123456789") + ->expectsQuestion("Enter the bot name (optional)", "") + ->expectsQuestion("Do you want to add a chat to this bot?", false) + ->expectsQuestion("Do you want to setup a webhook for this bot?", true) + ->assertExitCode(Command::SUCCESS); + + + expect(TelegraphBot::first()) + ->not->toBeNull() + ->token->toBe('123456789') + ->name->toBe('Bot #1'); + + Facade::assertRegisteredWebhook([ + 'max_connections' => 99, + ], false); +}); + +it('can register the new bot webhook settings its secret token', function () { + withfakeUrl(); + + Facade::fake([ + Telegraph::ENDPOINT_SET_WEBHOOK => [ + 'ok' => true, + ], + ]); + + artisan('telegraph:new-bot', ['--secret' => 'foo']) + ->expectsOutput('You are about to create a new Telegram Bot') + ->expectsQuestion("Please, enter the bot token", "123456789") + ->expectsQuestion("Enter the bot name (optional)", "") + ->expectsQuestion("Do you want to add a chat to this bot?", false) + ->expectsQuestion("Do you want to setup a webhook for this bot?", true) + ->assertExitCode(Command::SUCCESS); + + + expect(TelegraphBot::first()) + ->not->toBeNull() + ->token->toBe('123456789') + ->name->toBe('Bot #1'); + + Facade::assertRegisteredWebhook([ + 'secret_token' => 'foo', + ], false); +}); diff --git a/tests/Feature/Commands/SetTelegramWebhookCommandTest.php b/tests/Feature/Commands/SetTelegramWebhookCommandTest.php index 0844c76dd..dd4470d2b 100644 --- a/tests/Feature/Commands/SetTelegramWebhookCommandTest.php +++ b/tests/Feature/Commands/SetTelegramWebhookCommandTest.php @@ -22,6 +22,54 @@ ->assertExitCode(Command::SUCCESS); }); +test('can set telegram webhook dropping pending updates', function () { + withfakeUrl(); + bot(); + + Telegraph::fake(); + + /** @phpstan-ignore-next-line */ + artisan('telegraph:set-webhook', ['--drop-pending-updates' => true]) + ->expectsOutput("Webhook updated") + ->assertExitCode(Command::SUCCESS); + + Telegraph::assertRegisteredWebhook([ + 'drop_pending_updates' => true, + ], false); +}); + +test('can set telegram webhook settings its max connections', function () { + withfakeUrl(); + bot(); + + Telegraph::fake(); + + /** @phpstan-ignore-next-line */ + artisan('telegraph:set-webhook', ['--max-connections' => 99]) + ->expectsOutput("Webhook updated") + ->assertExitCode(Command::SUCCESS); + + Telegraph::assertRegisteredWebhook([ + 'max_connections' => 99, + ], false); +}); + +test('can set telegram webhook settings its secret token', function () { + withfakeUrl(); + bot(); + + Telegraph::fake(); + + /** @phpstan-ignore-next-line */ + artisan('telegraph:set-webhook', ['--secret' => 'foo']) + ->expectsOutput("Webhook updated") + ->assertExitCode(Command::SUCCESS); + + Telegraph::assertRegisteredWebhook([ + 'secret_token' => 'foo', + ], false); +}); + test('it requires bot id if there are more than one', function () { bots(2); diff --git a/tests/Unit/Concerns/InteractsWithWebhooksTest.php b/tests/Unit/Concerns/InteractsWithWebhooksTest.php index d696b35e7..fc51f802e 100644 --- a/tests/Unit/Concerns/InteractsWithWebhooksTest.php +++ b/tests/Unit/Concerns/InteractsWithWebhooksTest.php @@ -20,6 +20,27 @@ ->toMatchTelegramSnapshot(); }); +it('can register a webhook dropping pending updates', function () { + withfakeUrl(); + + expect(fn (Telegraph $telegraph) => $telegraph->bot(make_bot())->registerWebhook(dropPendingUpdates: true)) + ->toMatchTelegramSnapshot(); +}); + +it('can register a webhook setting its max connections', function () { + withfakeUrl(); + + expect(fn (Telegraph $telegraph) => $telegraph->bot(make_bot())->registerWebhook(maxConnections: 42)) + ->toMatchTelegramSnapshot(); +}); + +it('can register a webhook setting its secret token', function () { + withfakeUrl(); + + expect(fn (Telegraph $telegraph) => $telegraph->bot(make_bot())->registerWebhook(secretToken: 'super-secret-token')) + ->toMatchTelegramSnapshot(); +}); + it('requires an https url to register a webhook', function () { \DefStudio\Telegraph\Facades\Telegraph::bot(make_bot())->registerWebhook(); })->throws(TelegramWebhookException::class, 'You application must have a secure (https) url in order to accept webhook calls'); diff --git a/tests/Unit/Models/TelegraphBotTest.php b/tests/Unit/Models/TelegraphBotTest.php index 0565e5eb1..f254fe10e 100644 --- a/tests/Unit/Models/TelegraphBotTest.php +++ b/tests/Unit/Models/TelegraphBotTest.php @@ -47,6 +47,45 @@ Telegraph::assertRegisteredWebhook(); }); +it('can register its webhook dropping pending updates', function () { + withfakeUrl(); + + Telegraph::fake(); + $bot = make_bot(); + + $bot->registerWebhook(dropPendingUpdates: true)->send(); + + Telegraph::assertRegisteredWebhook([ + 'drop_pending_updates' => true, + ], false); +}); + +it('can register its webhook settings its max connections', function () { + withfakeUrl(); + + Telegraph::fake(); + $bot = make_bot(); + + $bot->registerWebhook(maxConnections: 34)->send(); + + Telegraph::assertRegisteredWebhook([ + 'max_connections' => 34, + ], false); +}); + +it('can register its webhook settings its secret token', function () { + withfakeUrl(); + + Telegraph::fake(); + $bot = make_bot(); + + $bot->registerWebhook(secretToken: 'bar')->send(); + + Telegraph::assertRegisteredWebhook([ + 'secret_token' => 'bar', + ], false); +}); + it('can unregister its webhook', function () { Telegraph::fake(); $bot = make_bot();