manual improvement 2 & add telegram bot

This commit is contained in:
hamid
2026-07-27 23:58:16 +03:30
parent baa3cc63cd
commit e6a8f93a1e
57 changed files with 4181 additions and 2484 deletions
+76
View File
@@ -0,0 +1,76 @@
import { config } from './env.js';
const API_BASE = `https://api.telegram.org/bot${config.botToken}`;
async function callApi(method, payload, { timeoutMs = 10_000 } = {}) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(`${API_BASE}/${method}`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(payload),
signal: controller.signal,
});
const body = await response.json().catch(() => ({}));
if (!response.ok || body.ok !== true) {
// Telegram carries the real failure in `description` (e.g. "chat not found" when the
// recipient never messaged the bot first) — surface it verbatim so the cause is obvious.
const reason = body.description ?? `HTTP ${response.status}`;
throw new Error(reason);
}
return body.result;
} finally {
clearTimeout(timer);
}
}
/** Sends one message to every configured chat id. Never rejects — per-recipient outcomes are returned. */
export async function broadcast(text) {
const results = await Promise.all(
config.chatIds.map(async (chatId) => {
try {
await callApi('sendMessage', { chat_id: chatId, text, disable_notification: false });
return { chatId, ok: true };
} catch (error) {
return { chatId, ok: false, error: error.message };
}
}),
);
return {
delivered: results.filter((r) => r.ok).map((r) => r.chatId),
failed: results.filter((r) => !r.ok).map(({ chatId, error }) => ({ chatId, error })),
};
}
/**
* Lists the chat ids that have messaged the bot recently, so a new recipient can be discovered
* without hunting through the Telegram API by hand. getUpdates only sees messages newer than the
* last 24h and returns nothing while a webhook is set.
*/
export async function discoverChatIds() {
const updates = await callApi('getUpdates', { limit: 100, timeout: 0 });
const seen = new Map();
for (const update of updates) {
const chat = update.message?.chat ?? update.edited_message?.chat ?? update.channel_post?.chat;
if (!chat) continue;
seen.set(String(chat.id), {
chat_id: String(chat.id),
type: chat.type,
username: chat.username ?? null,
title: chat.title ?? ([chat.first_name, chat.last_name].filter(Boolean).join(' ') || null),
});
}
return [...seen.values()];
}
export async function getBotIdentity() {
return callApi('getMe', {});
}