diff --git a/scripts/api.js b/scripts/api.js index a4bd19e..c93bf6c 100644 --- a/scripts/api.js +++ b/scripts/api.js @@ -104,6 +104,38 @@ export async function pushSnapshot(actor) { return result; } +/** + * Project each active Foundry module to a credit-only summary. Versions + * are deliberately omitted: this list is intended for a public credits + * page on the Frappe side and we don't want to leak dependency versions + * if the site is ever indexed. The Frappe endpoint also strips any + * version/compatibility fields server-side as defense-in-depth. + */ +function buildModuleInventory() { + const out = []; + for (const mod of game.modules.values()) { + if (!mod.active) continue; + const authors = (mod.authors || []) + .map((a) => ({ name: a?.name, url: a?.url || null })) + .filter((a) => a.name); + out.push({ + id: mod.id, + title: mod.title || mod.id, + url: mod.url || null, + authors, + }); + } + return out; +} + +export async function pushModuleInventory() { + const modules = buildModuleInventory(); + console.log(`[${MODULE_ID}] pushing module inventory (${modules.length} active)`); + const result = await bridgeFetch("receive_module_inventory", { modules }); + console.log(`[${MODULE_ID}] module inventory result:`, result); + return result; +} + export async function listScheduledSessions() { const result = await bridgeFetch("list_scheduled_sessions", {}); return result?.message ?? []; diff --git a/scripts/macros.js b/scripts/macros.js index 5c70c68..be15abe 100644 --- a/scripts/macros.js +++ b/scripts/macros.js @@ -11,6 +11,7 @@ import { MODULE_ID } from "./constants.js"; import { completeSession, listScheduledSessions, + pushModuleInventory, pushSnapshot, } from "./api.js"; @@ -91,7 +92,7 @@ async function pickSession(sessions) { }); } -function buildSummaryHtml(completion, snapshotResult) { +function buildSummaryHtml(completion, snapshotResult, moduleResult) { const parts = [ `
${escapeHtml(completion.completed_session)} marked Completed.
`, @@ -110,6 +111,15 @@ function buildSummaryHtml(completion, snapshotResult) { .join(""); parts.push(`Failed pushes:
Tracked ${moduleResult.upserted} active module(s) for credits.
`, + ); + } else if (moduleResult?.error) { + parts.push( + `Module inventory push failed: ${escapeHtml(moduleResult.error)}
`, + ); + } return parts.join(""); } @@ -152,8 +162,19 @@ export async function endSession() { const snapshotResult = await pushAllSnapshots(); + // Module inventory is non-critical — log and continue if the push fails + // so the session still gets marked Completed with a clean summary. + let moduleResult = null; + try { + const raw = await pushModuleInventory(); + moduleResult = raw?.message ?? null; + } catch (err) { + console.warn(`[${MODULE_ID}] module inventory push failed:`, err); + moduleResult = { error: err.message }; + } + await ChatMessage.create({ - content: buildSummaryHtml(completion, snapshotResult), + content: buildSummaryHtml(completion, snapshotResult, moduleResult), speaker: { alias: "Seitime Bridge" }, whisper: ChatMessage.getWhisperRecipients("GM").map((u) => u.id), }); diff --git a/scripts/main.js b/scripts/main.js index 06abc0b..bd4ce8a 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -10,7 +10,7 @@ import { MODULE_ID } from "./constants.js"; import { registerSettings } from "./settings.js"; -import { pushManifest, pushSnapshot, testConnection } from "./api.js"; +import { pushManifest, pushModuleInventory, pushSnapshot, testConnection } from "./api.js"; import { endSession, pushAllSnapshots } from "./macros.js"; let manifestTimerId = null; @@ -62,7 +62,7 @@ Hooks.once("init", () => { Hooks.once("ready", () => { const mod = game.modules.get(MODULE_ID); - const api = { pushManifest, pushSnapshot, testConnection, endSession, pushAllSnapshots }; + const api = { pushManifest, pushSnapshot, pushModuleInventory, testConnection, endSession, pushAllSnapshots }; mod.api = api; globalThis.seitimeBridge = api;