telegrambot/src/telegram.ts
alisaza 54a8dcb283 Add initial Telegram relay service for Iran→foreign delivery.
Thin Hono API so the Iran-hosted backend can POST ops alerts here and this VPS calls Telegram Bot API.
2026-09-13 13:38:36 +03:30

75 lines
2.1 KiB
TypeScript

import { z } from 'zod'
/** Telegram Bot API sendMessage text limit. */
export const TELEGRAM_TEXT_MAX = 4096
export const SendMessageBodySchema = z.object({
text: z.string().trim().min(1).max(TELEGRAM_TEXT_MAX),
/** Override default chat from env (group or private). */
chatId: z.string().trim().min(1).optional(),
/** Forum topic id; omit to use env default (if any). Pass null to force no topic. */
messageThreadId: z.number().int().positive().nullable().optional(),
disableWebPagePreview: z.boolean().optional().default(true),
})
export type SendMessageBody = z.infer<typeof SendMessageBodySchema>
export interface TelegramSendResult {
ok: true
messageId: number
}
export class TelegramApiError extends Error {
constructor(
readonly status: number,
readonly detail: string,
) {
super(`Telegram sendMessage failed: status=${status}`)
this.name = 'TelegramApiError'
}
}
export async function sendTelegramMessage(input: {
botToken: string
chatId: string
text: string
messageThreadId?: number
disableWebPagePreview: boolean
fetchImpl?: typeof fetch
}): Promise<TelegramSendResult> {
const fetchImpl = input.fetchImpl ?? fetch
const payload: Record<string, unknown> = {
chat_id: input.chatId,
text: input.text,
disable_web_page_preview: input.disableWebPagePreview,
}
if (input.messageThreadId !== undefined) {
payload.message_thread_id = input.messageThreadId
}
const response = await fetchImpl(
`https://api.telegram.org/bot${input.botToken}/sendMessage`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
},
)
if (!response.ok) {
const detail = await response.text().catch(() => '')
throw new TelegramApiError(response.status, detail.slice(0, 300))
}
const json = (await response.json()) as {
ok?: boolean
result?: { message_id?: number }
}
const messageId = json.result?.message_id
if (!json.ok || typeof messageId !== 'number') {
throw new TelegramApiError(502, 'Unexpected Telegram API response')
}
return { ok: true, messageId }
}