From a7b7aa350ab4bf54b2fcd8db3861273d3f8da193 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 3 Nov 2026 22:34:39 +0100 Subject: [PATCH] fix(telegram): send button links as text when Telegram rejects the button URL Closes #8269 Co-Authored-By: Claude Opus 5.5 --- app/Jobs/SendMessageToTelegramJob.php | 23 ++++++++++++------ .../Feature/TelegramTestNotificationTest.php | 24 +++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/Jobs/SendMessageToTelegramJob.php b/app/Jobs/SendMessageToTelegramJob.php index cf7d5c25c3..bbb3cd087a 100644 --- a/app/Jobs/SendMessageToTelegramJob.php +++ b/app/Jobs/SendMessageToTelegramJob.php @@ -81,12 +81,6 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue } } $payload = [ - // 'parse_mode' => 'markdown', - 'reply_markup' => json_encode([ - 'inline_keyboard' => [ - [...$inlineButtons], - ], - ]), 'chat_id' => trim($this->chatId), 'text' => $this->text, ]; @@ -94,6 +88,21 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue $payload['message_thread_id'] = $this->threadId; } - return $this->sendNotificationRequest('Telegram', $url, fn () => Http::post($url, $payload)); + $response = $this->sendNotificationRequest('Telegram', $url, fn () => Http::post($url, [ + ...$payload, + 'reply_markup' => json_encode(['inline_keyboard' => [$inlineButtons]]), + ])); + + // Telegram rejects the whole message when it cannot open a button URL (for example localhost or an IP address without a domain). + if ($inlineButtons !== [] && $response->status() === 400 + && Str::contains((string) $response->json('description'), ['button url', 'button_url'], true)) { + $links = collect($inlineButtons)->map(fn (array $button) => "{$button['text']}: {$button['url']}")->implode("\n"); + $response = $this->sendNotificationRequest('Telegram', $url, fn () => Http::post($url, [ + ...$payload, + 'text' => "{$this->text}\n\n{$links}", + ])); + } + + return $response; } } diff --git a/tests/Feature/TelegramTestNotificationTest.php b/tests/Feature/TelegramTestNotificationTest.php index 9716aabfb0..4b57af9da8 100644 --- a/tests/Feature/TelegramTestNotificationTest.php +++ b/tests/Feature/TelegramTestNotificationTest.php @@ -74,3 +74,27 @@ it('tells the user to check the bot token when Telegram does not find the bot', ->assertDispatched('error', 'Telegram rejected the test notification.', 'The Bot API token is not valid. Copy it again from @BotFather.') ->assertNotDispatched('success'); }); + +it('sends the dashboard link as text when Telegram rejects the button URL', function () { + Http::fakeSequence('api.telegram.org/*') + ->push(['ok' => false, 'error_code' => 400, 'description' => 'Bad Request: BUTTON_URL_INVALID'], 400) + ->push(['ok' => true, 'result' => []]); + + Livewire::test(TelegramNotification::class) + ->call('sendTestNotification') + ->assertDispatched('success', 'Test notification sent.'); + + $requests = Http::recorded(); + expect($requests)->toHaveCount(2) + ->and($requests[0][0]->data())->toHaveKey('reply_markup') + ->and($requests[1][0]->data())->not->toHaveKey('reply_markup') + ->and($requests[1][0]['text'])->toBe("Coolify: This is a test Telegram notification from Coolify.\n\nGo to your dashboard: ".base_url()); +}); + +it('does not resend a notification that Telegram rejects for another reason', function () { + Http::fake(['api.telegram.org/*' => Http::response(['ok' => false, 'description' => 'Bad Request: chat not found'], 400)]); + + Livewire::test(TelegramNotification::class)->call('sendTestNotification'); + + Http::assertSentCount(1); +});