feat(settings): добавить Bot API URL и ячейку ошибок уведомлений
Docker / build (push) Failing after 26s

Монитор больше не ставит «Внимание» по скрытому счётчику. Telegram можно слать через локальный telegram-bot-api.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-08-22 02:29:07 +07:00
co-authored by Cursor
parent 9214d6fbab
commit 9e0311b53a
21 changed files with 235 additions and 20 deletions
+1
View File
@@ -117,6 +117,7 @@ export function importJsonSnapshot(data: BackupPayload): void {
syncTariffsIntervalMinutes: Number(s.syncTariffsIntervalMinutes) || 1440,
telegramBotToken: String(s.telegramBotToken ?? ''),
telegramChatId: String(s.telegramChatId ?? ''),
telegramApiUrl: String(s.telegramApiUrl ?? ''),
telegramMessageThreadId: String(s.telegramMessageThreadId ?? ''),
notifyPaymentExpiryEnabled: s.notifyPaymentExpiryEnabled ? 1 : 0,
notifyNewTariffsEnabled: s.notifyNewTariffsEnabled ? 1 : 0,
@@ -9,7 +9,13 @@ export async function deliverTelegram(
const token = settings.telegramBotToken?.trim()
const chatId = settings.telegramChatId?.trim()
if (!token || !chatId) return { ok: false, error: 'Telegram не настроен' }
return sendTelegramMessage(token, chatId, messageHtml, settings.telegramMessageThreadId)
return sendTelegramMessage(
token,
chatId,
messageHtml,
settings.telegramMessageThreadId,
settings.telegramApiUrl,
)
}
export async function deliverWebhook(
@@ -27,6 +27,7 @@ export interface NotificationPayload {
export interface SettingsNotifyRow {
telegramBotToken?: string | null
telegramChatId?: string | null
telegramApiUrl?: string | null
telegramMessageThreadId?: string | null
webhookUrl?: string | null
webhookEnabled?: number | boolean | null
+35 -1
View File
@@ -1,5 +1,10 @@
import { describe, expect, it } from 'vitest'
import { formatTelegramApiError, telegramErrorHint } from './telegram.js'
import {
formatTelegramApiError,
telegramErrorHint,
telegramSendMessageUrl,
} from './telegram.js'
import { DEFAULT_TELEGRAM_API_URL } from '@cfdm/shared/contracts/settings'
describe('telegramErrorHint', () => {
it('maps thread not found', () => {
@@ -36,3 +41,32 @@ describe('formatTelegramApiError', () => {
expect(msg).toBe('-1001: invalid payload')
})
})
describe('telegramSendMessageUrl', () => {
it('uses cloud origin by default', () => {
expect(telegramSendMessageUrl('TOKEN')).toBe(
`${DEFAULT_TELEGRAM_API_URL}/botTOKEN/sendMessage`,
)
})
it('builds local HTTP origin', () => {
expect(telegramSendMessageUrl('TOKEN', 'http://127.0.0.1:8081')).toBe(
'http://127.0.0.1:8081/botTOKEN/sendMessage',
)
})
it('builds HTTPS reverse-proxy origin', () => {
expect(telegramSendMessageUrl('TOKEN', 'https://bots.example.com')).toBe(
'https://bots.example.com/botTOKEN/sendMessage',
)
})
it('strips trailing slash and /bot suffix', () => {
expect(telegramSendMessageUrl('TOKEN', 'http://127.0.0.1:8081/')).toBe(
'http://127.0.0.1:8081/botTOKEN/sendMessage',
)
expect(telegramSendMessageUrl('TOKEN', 'http://127.0.0.1:8081/bot')).toBe(
'http://127.0.0.1:8081/botTOKEN/sendMessage',
)
})
})
+13 -1
View File
@@ -2,6 +2,17 @@
* Telegram Bot API — отправка уведомлений
*/
import {
DEFAULT_TELEGRAM_API_URL,
normalizeTelegramApiUrl,
} from '@cfdm/shared/contracts/settings'
export { DEFAULT_TELEGRAM_API_URL, normalizeTelegramApiUrl }
export function telegramSendMessageUrl(token: string, apiUrl?: string | null): string {
return `${normalizeTelegramApiUrl(apiUrl)}/bot${token.trim()}/sendMessage`
}
export interface TelegramSendResult {
ok: boolean
error?: string
@@ -55,6 +66,7 @@ export async function sendTelegramMessage(
chatIds: string | string[],
text: string,
messageThreadId?: string | number | null,
apiUrl?: string | null,
): Promise<TelegramSendResult> {
if (!token?.trim() || !text?.trim()) {
return { ok: false, error: 'Пустой токен или текст' }
@@ -75,7 +87,7 @@ export async function sendTelegramMessage(
}
if (Number.isFinite(threadId)) payload.message_thread_id = threadId
const url = `https://api.telegram.org/bot${token.trim()}/sendMessage`
const url = telegramSendMessageUrl(token, apiUrl)
const errors: string[] = []
let anyOk = false