/** Cross-process Telegram update inbox helpers. */ import { existsSync } from "node:fs"; import { appendFile, mkdir, readFile, stat, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; export interface TelegramInboxItem { update: TUpdate; receivedAt: number; } export interface TelegramInboxDelta { text: string; cursor: number; } export interface TelegramInboxParseResult { valid: TelegramInboxItem[]; invalid: Array<{ line: string; error: string }>; } export async function ensureTelegramInbox(path: string): Promise { await mkdir(dirname(path), { recursive: true }); if (!existsSync(path)) await writeFile(path, "", { mode: 0o600 }); } export async function appendTelegramInboxItem( path: string, item: TelegramInboxItem, ): Promise { await ensureTelegramInbox(path); await appendFile(path, JSON.stringify(item) + "\n", "utf8"); } export async function getTelegramInboxSize(path: string): Promise { await ensureTelegramInbox(path); return (await stat(path)).size; } export async function readTelegramInboxDelta(path: string, cursor: number): Promise { await ensureTelegramInbox(path); const content = await readFile(path); const safeCursor = Math.max(0, Math.min(cursor, content.length)); return { text: content.subarray(safeCursor).toString("utf8"), cursor: content.length, }; } export function parseTelegramInboxLines(text: string): TelegramInboxParseResult { const valid: TelegramInboxItem[] = []; const invalid: Array<{ line: string; error: string }> = []; for (const line of text.split("\n")) { if (!line.trim()) continue; try { valid.push(JSON.parse(line) as TelegramInboxItem); } catch (error) { invalid.push({ line, error: error instanceof Error ? error.message : String(error) }); } } return { valid, invalid }; } export interface TelegramInboxConsumerOptions { inboxPath: string; initialCursor?: number; onUpdate: (update: TUpdate) => Promise | void; onInvalidLine: (invalid: { line: string; error: string }) => void; onCursor?: (cursor: number) => Promise | void; } export interface TelegramInboxConsumer { pollOnce: () => Promise; start: () => void; stop: () => void; } export function createTelegramInboxConsumer( options: TelegramInboxConsumerOptions, ): TelegramInboxConsumer { let cursor = options.initialCursor ?? 0; let timer: ReturnType | undefined; const pollOnce = async () => { const delta = await readTelegramInboxDelta(options.inboxPath, cursor); const parsed = parseTelegramInboxLines(delta.text); for (const invalid of parsed.invalid) options.onInvalidLine(invalid); for (const item of parsed.valid) await options.onUpdate(item.update); cursor = delta.cursor; await options.onCursor?.(cursor); }; return { pollOnce, start: () => { if (timer) return; timer = setInterval(() => { void pollOnce(); }, 1000); timer.unref?.(); }, stop: () => { if (timer) clearInterval(timer); timer = undefined; }, }; }