Files
jsc-marketplace/plugins/jsc-mod-companion/hooks/companion/materials.ts
T
adminandClaude Sonnet 5.5 7e6e323473 feat: companion 作息、主動發話、適應度與外部素材
- 作息與疲勞、想念累積與時間感,主動發話沿用 care.ts 的冷卻、日限、靜音
- 想家與適應度(9.13)
- 獨立腳本 fetch-materials.mjs 由系統排程寫入快取,hook 只讀、不連網
- 新增 /jsc-mod-companion:materials 與 setup location=、feeds=
- 天氣微調心情基準,新聞依適應度每天最多 2 則並寫入情節記憶
- 版本 0.0.5

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 09:29:46 +00:00

32 lines
1.5 KiB
TypeScript

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<Materials> {
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))
}