import { readJson } from './fs' import type { Fs } from './fs' import { MATERIALS as M, WEATHER_MOOD } from './rules' // 外部素材快取:由系統排程執行的 scripts/fetch-materials.mjs 寫入,hook 只讀、不連網。 // 只存標題、摘要、連結與時間(at 為抓取時間,毫秒)。 export type Item = { title: string; summary: string; link: string; at: number } export type Materials = { weather: Item | null; news: Item[] } export const EMPTY: Materials = { weather: null, news: [] } const isItem = (x: unknown): x is Item => { const i = x as Item return !!i && typeof i.title === 'string' && typeof i.summary === 'string' && typeof i.link === 'string' && typeof i.at === 'number' } /** 讀快取並丟掉過期項目:天氣 6 小時、新聞 24 小時;檔案缺失或壞掉就是沒查 */ export async function readMaterials(fs: Fs, path: string, now: number): Promise { const raw = await readJson<{ weather?: unknown; news?: unknown }>(fs, path) if (!raw) return EMPTY const weather = isItem(raw.weather) && now - raw.weather.at < M.weatherTtlMs ? raw.weather : null const news = (Array.isArray(raw.news) ? raw.news : []).filter(isItem).filter(n => now - n.at < M.newsTtlMs) return { weather, news } } /** 天氣對心情基準的微調;未知狀況為 0,且不超過上限 */ export function weatherShift(w: Item | null): number { if (!w) return 0 const v = WEATHER_MOOD[w.title] ?? 0 return Math.max(-M.weatherCap, Math.min(M.weatherCap, v)) }