import type { Bot, Group, RunActivityRow } from "@rakazo/contracts"; export type JetztRunItem = { kind: "run"; run: RunActivityRow; }; export type JetztUnreadItem = { kind: "unread"; chatKind: "bot" | "group"; chat: Bot | Group; }; export type JetztItem = JetztRunItem | JetztUnreadItem; export type WeiterItem = { chatKind: "bot" | "group"; chat: Bot | Group; }; export type HeuteBriefData = { jetzt: JetztItem[]; weiter: WeiterItem[]; actionCount: number; }; export function isToday(iso: string, now = new Date()): boolean { const date = new Date(iso); if (Number.isNaN(date.getTime())) return false; return ( date.getDate() === now.getDate() && date.getMonth() === now.getMonth() && date.getFullYear() === now.getFullYear() ); } export function cleanBriefPreview(raw?: string | null): string { if (!raw) return ""; let text = raw; text = text.replace(/^#{1,6}\s+/gm, ""); text = text.replace(/\[(?:to|RECOVERY PROTOCOL|PO)[^\]]*\]/gi, ""); text = text.replace(/[*_`#]/g, ""); text = text.replace(/\\/g, ""); return text.replace(/\s+/g, " ").trim(); } function runTargetKey(run: RunActivityRow): string { return run.groupId ? `group:${run.groupId}` : `bot:${run.botId}`; } function chatTargetKey(chatKind: "bot" | "group", chatId: string): string { return chatKind === "group" ? `group:${chatId}` : `bot:${chatId}`; } function isActionableRun(run: RunActivityRow): boolean { return run.status === "waiting_input" || run.status === "waiting_takeover"; } function isFailedRoutine(run: RunActivityRow): boolean { return run.trigger === "routine" && run.status === "failed"; } export function buildHeuteBrief(params: { bots: Bot[]; groups: Group[]; activeRuns: RunActivityRow[]; recentRuns: RunActivityRow[]; now?: Date; }): HeuteBriefData { const { bots, groups, activeRuns, recentRuns, now = new Date() } = params; const jetzt: JetztItem[] = []; const coveredTargets = new Set(); const actionableRuns = [ ...activeRuns.filter(isActionableRun), ...recentRuns.filter(isFailedRoutine), ]; const seenRunIds = new Set(); for (const run of actionableRuns) { if (seenRunIds.has(run.runId)) continue; seenRunIds.add(run.runId); jetzt.push({ kind: "run", run }); coveredTargets.add(runTargetKey(run)); } const unreadChats: JetztUnreadItem[] = [ ...bots.filter((bot) => bot.unread).map((bot) => ({ kind: "unread" as const, chatKind: "bot" as const, chat: bot })), ...groups.filter((group) => group.unread).map((group) => ({ kind: "unread" as const, chatKind: "group" as const, chat: group, })), ]; for (const item of unreadChats) { const key = chatTargetKey(item.chatKind, item.chat.id); if (coveredTargets.has(key)) continue; jetzt.push(item); coveredTargets.add(key); } const weiterCandidates: WeiterItem[] = [ ...bots .filter((bot) => isToday(bot.updatedAt, now)) .map((bot) => ({ chatKind: "bot" as const, chat: bot })), ...groups .filter((group) => isToday(group.updatedAt, now)) .map((group) => ({ chatKind: "group" as const, chat: group })), ]; weiterCandidates.sort( (a, b) => new Date(b.chat.updatedAt).getTime() - new Date(a.chat.updatedAt).getTime(), ); const weiter = weiterCandidates .filter((item) => !coveredTargets.has(chatTargetKey(item.chatKind, item.chat.id))) .slice(0, 5); return { jetzt, weiter, actionCount: jetzt.length, }; }