1
0
Fork 0

fix(bot): refresh roll-call date changes

This commit is contained in:
Jason Fraley 2026-08-28 00:20:24 -04:00
parent 055db8d0f0
commit f6d42399d4
2 changed files with 26 additions and 17 deletions

View file

@ -66,7 +66,10 @@ export const handleButtonInteraction = async (
await setMissionAttendance(payload, { missionId, userId: user.id, response });
const summary = await getMissionAttendance(payload, missionId);
const hash = attendanceHash(await fetchAttendanceDocs(payload, missionId));
const hash = attendanceHash(
await fetchAttendanceDocs(payload, missionId),
mission.classification.startDateTime,
);
await interaction.message.edit({
embeds: [renderAttendanceEmbed(mission, summary)],

View file

@ -28,12 +28,6 @@ const missionTitle = (mission: Mission): string =>
const memberName = (user: User): string => user.displayName ?? user.username;
const isStillFuture = (mission: Mission): boolean => {
const start = new Date(mission.classification.startDateTime).getTime();
const end = start + mission.classification.estimatedDuration * 60_000;
return end > Date.now();
};
/**
* Roll call is sent once: as soon as the mission is Ready, or three days
* before a Scheduled mission, whichever happens first.
@ -59,17 +53,18 @@ const operationTypeLabel = (type: Mission["operationType"]): string => {
};
/**
* Deterministic fingerprint of the attendance state so reconcile can skip
* embeds that haven't changed (anti-loop).
* 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.
*/
export const attendanceHash = (docs: MissionAttendance[]): string => {
export const attendanceHash = (docs: MissionAttendance[], startDateTime?: string): string => {
const rows = docs
.map((doc) => {
const userId = typeof doc.user === "object" ? doc.user.id : doc.user;
return [userId, doc.response] as const;
})
.sort((a, b) => a[0] - b[0] || a[1].localeCompare(b[1]));
return JSON.stringify(rows);
return JSON.stringify({ rows, startDateTime: startDateTime ?? null });
};
export const fetchAttendanceDocs = async (
@ -97,17 +92,23 @@ export const getEligibleMissions = async (payload: Payload): Promise<Mission[]>
limit: MAX_ELIGIBLE_MISSIONS,
where: {
and: [
{ "classification.startDateTime": { greater_than: new Date().toISOString() } },
{ "ownershipAndStatus.visibility": { equals: "unit" } },
{
or: [
{ "ownershipAndStatus.status": { equals: "Ready" } },
{
and: [
{ "classification.startDateTime": { greater_than: new Date().toISOString() } },
{ "ownershipAndStatus.status": { equals: "Ready" } },
],
},
{
and: [
{ "classification.startDateTime": { greater_than: new Date().toISOString() } },
{ "ownershipAndStatus.status": { equals: "Scheduled" } },
{ "classification.startDateTime": { less_than_equal: rollCallCutoff } },
],
},
{ discordMessageId: { exists: true } },
],
},
],
@ -200,7 +201,10 @@ const postEmbed = async (
mission: Mission,
): Promise<void> => {
const summary = await getMissionAttendance(payload, mission.id);
const hash = attendanceHash(await fetchAttendanceDocs(payload, mission.id));
const hash = attendanceHash(
await fetchAttendanceDocs(payload, mission.id),
mission.classification.startDateTime,
);
const channel = await getOpsChannel(client, opsChannelId);
const message = await channel.send({
embeds: [renderAttendanceEmbed(mission, summary)],
@ -257,7 +261,10 @@ const manageMission = async (
return;
}
const hash = attendanceHash(await fetchAttendanceDocs(payload, mission.id));
const hash = attendanceHash(
await fetchAttendanceDocs(payload, mission.id),
mission.classification.startDateTime,
);
if (hash === mission.discordAttendanceHash) {
return;
}
@ -297,7 +304,6 @@ export const reconcileMission = async (
if (
mission.ownershipAndStatus.visibility !== "unit" ||
!["Ready", "Scheduled"].includes(mission.ownershipAndStatus.status) ||
!isStillFuture(mission) ||
(!isRollCallDue(mission) && !mission.discordMessageId)
) {
return;
@ -353,7 +359,7 @@ export const reconcile = async (client: Client, payload: Payload): Promise<void>
}
const eligible = (await getEligibleMissions(payload)).filter(
(mission) => isStillFuture(mission) && (isRollCallDue(mission) || !!mission.discordMessageId),
(mission) => isRollCallDue(mission) || !!mission.discordMessageId,
);
for (const mission of eligible) {
try {