mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
189 lines
4.8 KiB
TypeScript
189 lines
4.8 KiB
TypeScript
import { log } from "../../lib/log";
|
|
import type { SendResult } from "../types";
|
|
|
|
const TELEGRAM_API = "https://api.telegram.org";
|
|
|
|
interface TelegramResponse {
|
|
ok?: boolean;
|
|
description?: string;
|
|
result?: { message_id?: number };
|
|
}
|
|
|
|
async function post(
|
|
token: string,
|
|
method: string,
|
|
body: Record<string, unknown>,
|
|
chatId: string,
|
|
): Promise<SendResult> {
|
|
const url = `${TELEGRAM_API}/bot${token}/${method}`;
|
|
let lastStatus = 0;
|
|
let lastError = "";
|
|
|
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
try {
|
|
const res = await fetch(url, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
});
|
|
lastStatus = res.status;
|
|
const data = (await res.json().catch(() => null)) as TelegramResponse | null;
|
|
|
|
if (res.status === 429) {
|
|
const retryAfter = (data as { retry_after?: number })?.retry_after ?? 1;
|
|
lastError = data?.description ?? `Rate limited (retry_after=${retryAfter})`;
|
|
await new Promise((r) => setTimeout(r, retryAfter * 1000));
|
|
continue;
|
|
}
|
|
|
|
if (!res.ok) {
|
|
lastError = data?.description ?? `HTTP ${res.status}`;
|
|
log.error(
|
|
{ status: res.status, err: lastError, chatId, attempts: attempt + 1 },
|
|
"Telegram API error",
|
|
);
|
|
return {
|
|
ok: false,
|
|
error: lastError,
|
|
errorCode: res.status >= 500 ? "TELEGRAM_5XX" : "TELEGRAM_ERROR",
|
|
status: res.status,
|
|
attempts: attempt + 1,
|
|
};
|
|
}
|
|
|
|
return {
|
|
ok: true,
|
|
status: res.status,
|
|
messageId: data?.result?.message_id != null ? String(data.result.message_id) : undefined,
|
|
attempts: attempt + 1,
|
|
};
|
|
} catch (err) {
|
|
lastError = err instanceof Error ? err.message : String(err);
|
|
log.error({ err, chatId, attempts: attempt + 1 }, "Failed to send Telegram message");
|
|
if (attempt === 2) {
|
|
return {
|
|
ok: false,
|
|
error: lastError,
|
|
errorCode: "NETWORK",
|
|
status: lastStatus,
|
|
attempts: attempt + 1,
|
|
};
|
|
}
|
|
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
|
|
}
|
|
}
|
|
|
|
return {
|
|
ok: false,
|
|
error: lastError || "Failed to send Telegram message",
|
|
errorCode: "RETRIES",
|
|
status: lastStatus,
|
|
attempts: 3,
|
|
};
|
|
}
|
|
|
|
export async function sendMessage(
|
|
token: string,
|
|
chatId: string,
|
|
text: string,
|
|
topicId?: string,
|
|
linkPreviewUrl?: string,
|
|
): Promise<SendResult> {
|
|
if (!token) {
|
|
return { ok: false, error: "TELEGRAM_TOKEN not configured", errorCode: "NO_TOKEN" };
|
|
}
|
|
const body: Record<string, unknown> = {
|
|
chat_id: chatId,
|
|
text,
|
|
parse_mode: "HTML",
|
|
disable_web_page_preview: !linkPreviewUrl,
|
|
};
|
|
if (linkPreviewUrl) {
|
|
body.link_preview_options = {
|
|
url: linkPreviewUrl,
|
|
prefer_small_media: true,
|
|
show_above_text: true,
|
|
};
|
|
}
|
|
if (topicId) {
|
|
body.message_thread_id = Number(topicId);
|
|
}
|
|
return post(token, "sendMessage", body, chatId);
|
|
}
|
|
|
|
export async function sendPhoto(
|
|
token: string,
|
|
chatId: string,
|
|
photoUrl: string,
|
|
caption?: string,
|
|
topicId?: string,
|
|
): Promise<SendResult> {
|
|
if (!token) {
|
|
return { ok: false, error: "TELEGRAM_TOKEN not configured", errorCode: "NO_TOKEN" };
|
|
}
|
|
const body: Record<string, unknown> = {
|
|
chat_id: chatId,
|
|
photo: photoUrl,
|
|
parse_mode: "HTML",
|
|
};
|
|
if (caption) {
|
|
body.caption = caption;
|
|
}
|
|
if (topicId) {
|
|
body.message_thread_id = Number(topicId);
|
|
}
|
|
return post(token, "sendPhoto", body, chatId);
|
|
}
|
|
|
|
export async function editMessageText(
|
|
token: string,
|
|
chatId: string,
|
|
messageId: string,
|
|
text: string,
|
|
topicId?: string,
|
|
linkPreviewUrl?: string,
|
|
): Promise<SendResult> {
|
|
if (!token) {
|
|
return { ok: false, error: "TELEGRAM_TOKEN not configured", errorCode: "NO_TOKEN" };
|
|
}
|
|
const body: Record<string, unknown> = {
|
|
chat_id: chatId,
|
|
message_id: messageId,
|
|
text,
|
|
parse_mode: "HTML",
|
|
disable_web_page_preview: !linkPreviewUrl,
|
|
};
|
|
if (linkPreviewUrl) {
|
|
body.link_preview_options = {
|
|
url: linkPreviewUrl,
|
|
prefer_small_media: true,
|
|
show_above_text: true,
|
|
};
|
|
}
|
|
if (topicId) {
|
|
body.message_thread_id = Number(topicId);
|
|
}
|
|
return post(token, "editMessageText", body, chatId);
|
|
}
|
|
|
|
export async function editMessageCaption(
|
|
token: string,
|
|
chatId: string,
|
|
messageId: string,
|
|
caption: string,
|
|
topicId?: string,
|
|
): Promise<SendResult> {
|
|
if (!token) {
|
|
return { ok: false, error: "TELEGRAM_TOKEN not configured", errorCode: "NO_TOKEN" };
|
|
}
|
|
const body: Record<string, unknown> = {
|
|
chat_id: chatId,
|
|
message_id: messageId,
|
|
caption,
|
|
parse_mode: "HTML",
|
|
};
|
|
if (topicId) {
|
|
body.message_thread_id = Number(topicId);
|
|
}
|
|
return post(token, "editMessageCaption", body, chatId);
|
|
}
|