feat(bot): link roll-call embeds to the mission page with a summary
The embed title links to the mission page and an 'Open the op' field carries the URL; the mission summary is shown under the title, truncated to 300 characters with an ellipsis. The attendance hash gains an embed schema version so live roll-calls re-render exactly once.
This commit is contained in:
parent
efae94c8a9
commit
4fad7cd259
1 changed files with 21 additions and 2 deletions
|
|
@ -64,8 +64,22 @@ const operationTypeLabel = (type: Mission["operationType"]): string => {
|
|||
/**
|
||||
* Deterministic fingerprint of the attendance state and scheduled start time
|
||||
* so reconcile can skip embeds that haven't changed (anti-loop), while still
|
||||
* detecting mission date changes.
|
||||
* detecting mission date changes. The schema version forces one re-render of
|
||||
* already-live roll-calls when the embed layout changes (v2 added the
|
||||
* mission-page link); bump it again for future layout-only changes.
|
||||
*/
|
||||
const EMBED_SCHEMA_VERSION = 3;
|
||||
|
||||
const SUMMARY_MAX_CHARS = 300;
|
||||
|
||||
const renderSummaryForEmbed = (summary: string | null | undefined): string => {
|
||||
if (!summary?.trim()) return "";
|
||||
const text = summary.trim();
|
||||
return text.length > SUMMARY_MAX_CHARS
|
||||
? `${text.slice(0, SUMMARY_MAX_CHARS).trimEnd()}...`
|
||||
: text;
|
||||
};
|
||||
|
||||
export const attendanceHash = (docs: MissionAttendance[], startDateTime?: string): string => {
|
||||
const rows = docs
|
||||
.map((doc) => {
|
||||
|
|
@ -73,7 +87,7 @@ export const attendanceHash = (docs: MissionAttendance[], startDateTime?: string
|
|||
return [userId, doc.response] as const;
|
||||
})
|
||||
.sort((a, b) => a[0] - b[0] || a[1].localeCompare(b[1]));
|
||||
return JSON.stringify({ rows, startDateTime: startDateTime ?? null });
|
||||
return JSON.stringify({ rows, startDateTime: startDateTime ?? null, v: EMBED_SCHEMA_VERSION });
|
||||
};
|
||||
|
||||
export const fetchAttendanceDocs = async (
|
||||
|
|
@ -172,11 +186,15 @@ export const renderAttendanceEmbed = (
|
|||
summary: MissionAttendanceSummary,
|
||||
): EmbedBuilder => {
|
||||
const startUnix = Math.floor(new Date(mission.classification.startDateTime).getTime() / 1000);
|
||||
const missionUrl = `${botConfig.appUrl}/intelligence/missions/${mission.id}`;
|
||||
const names = (users: User[]): string => users.map(memberName).join("\n").slice(0, 1024) || "-";
|
||||
const summaryText = renderSummaryForEmbed(mission.summary);
|
||||
return new EmbedBuilder()
|
||||
.setTitle(missionTitle(mission))
|
||||
.setURL(missionUrl)
|
||||
.setDescription(
|
||||
[
|
||||
...(summaryText ? [summaryText, ""] : []),
|
||||
`**Operation Type:** ${operationTypeLabel(mission.operationType)}`,
|
||||
`**Starts:** <t:${startUnix}:F> (<t:${startUnix}:R>)`,
|
||||
`**Max Players:** ${mission.missionRoles.maxPlayers}`,
|
||||
|
|
@ -191,6 +209,7 @@ export const renderAttendanceEmbed = (
|
|||
inline: true,
|
||||
},
|
||||
{ name: `❌ No (${summary.counts.no})`, value: names(summary.no), inline: true },
|
||||
{ name: "Open the op", value: missionUrl },
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue