// Telegram delivery for the daily digest. // // Setup: // 1. Message @BotFather on Telegram → /newbot → copy the token // 2. Put TELEGRAM_BOT= in .env (TELEGRAM_TOKEN also accepted) // 3. Send your bot any message ("hi"), then run: bun run telegram.ts --setup // That prints your chat ID; put it in .env as TELEGRAM_CHAT_ID= const env = Object.fromEntries( (await Bun.file(".env").text()) .split("\n") .filter((l) => l.includes("=") && !l.trimStart().startsWith("#")) .map((l) => { const i = l.indexOf("="); return [l.slice(0, i).trim(), l.slice(i + 1).trim()]; }) ); export const TOKEN = env.TELEGRAM_BOT ?? env.TELEGRAM_TOKEN ?? ""; export const CHAT_ID = env.TELEGRAM_CHAT_ID ?? env.TELEGRAM_CHAT ?? ""; const API = (m: string) => `https://api.telegram.org/bot${TOKEN}/${m}`; /** Telegram hard-caps a message at 4096 chars; split on line boundaries. */ function chunk(text: string, limit = 3900): string[] { const out: string[] = []; let buf = ""; for (const line of text.split("\n")) { if (buf.length + line.length + 1 > limit) { out.push(buf); buf = ""; } buf += (buf ? "\n" : "") + line; } if (buf) out.push(buf); return out; } export function escapeHtml(s: string): string { return s.replace(/&/g, "&").replace(//g, ">"); } export async function send(text: string): Promise { if (!TOKEN || !CHAT_ID) { throw new Error("TELEGRAM_BOT / TELEGRAM_CHAT_ID missing from .env — see telegram.ts header"); } for (const part of chunk(text)) { const res = await fetch(API("sendMessage"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ chat_id: CHAT_ID, text: part, parse_mode: "HTML", link_preview_options: { is_disabled: true }, }), }); if (!res.ok) throw new Error(`Telegram ${res.status}: ${await res.text()}`); await Bun.sleep(400); // stay under the ~1 msg/sec limit } } // --setup: discover the chat ID from recent messages to the bot if (import.meta.main && process.argv.includes("--setup")) { if (!TOKEN) { console.error("Add TELEGRAM_BOT= to .env first (get one from @BotFather)."); process.exit(1); } const res = await fetch(API("getUpdates")); const data = (await res.json()) as any; const chats = new Map(); for (const u of data.result ?? []) { const c = u.message?.chat ?? u.channel_post?.chat; if (c) chats.set(String(c.id), c.username ?? c.title ?? c.first_name ?? ""); } if (!chats.size) { console.log("No messages found. Send your bot a message in Telegram, then re-run this."); process.exit(1); } console.log("Add this to .env:\n"); for (const [id, name] of chats) console.log(`TELEGRAM_CHAT_ID=${id} # ${name}`); }