1
0
Fork 0
polaris-task-force/src/bot/commands/remindEvaluations.ts
Z8MB1E e3c4eada09 feat(bot): add post-mission evaluation reminder DMs
Sends one-shot DMs once a mission is evaluable asking members to rate their leadership (and leaders to rate subordinates), gated by a new evaluationRemindersSentAt marker on Missions with its migration. Adds the /remind-evaluations staff command (manual trigger/sweep) and DISCORD_EVALUATION_POLL_MS config; updates bot and root docs.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-09-06 01:24:17 -04:00

116 lines
4 KiB
TypeScript

import { MessageFlags, SlashCommandBuilder, type Client } from "discord.js";
import type { Payload } from "payload";
import type { BotCommand } from "./index";
import { isStaff } from "@/bot/lib/roles";
import { processMission, reconcile } from "@/bot/services/evaluationReminders";
import { isMissionEvaluable } from "@/lib/evaluations";
import type { Mission } from "@/payload-types";
const missionLabel = (mission: Mission): string =>
mission.codeName ? `${mission.name} (${mission.codeName})` : mission.name;
const resolveTargetMissions = async (
payload: Payload,
input: string,
): Promise<{ mission?: Mission; error?: string }> => {
const trimmed = input.trim();
try {
const mission = await payload.findByID({
collection: "missions",
id: trimmed,
overrideAccess: true,
depth: 0,
});
return { mission: mission as unknown as Mission };
} catch {
// Not an ID — fall through to a name / code-name search.
}
const res = await payload.find({
collection: "missions",
overrideAccess: true,
depth: 0,
limit: 6,
where: {
or: [{ name: { like: trimmed } }, { codeName: { like: trimmed } }],
},
});
const docs = res.docs as unknown as Mission[];
if (docs.length === 0) {
return { error: `No mission matches "${trimmed}" (searched by ID, name, and code name).` };
}
if (docs.length > 1) {
const list = docs.map((m) => `• ${missionLabel(m)} — #${m.id}`).join("\n");
return { error: `Multiple missions match "${trimmed}". Be more specific:\n${list}` };
}
return { mission: docs[0] };
};
export const remindEvaluationsCommand: BotCommand = {
builder: new SlashCommandBuilder()
.setName("remind-evaluations")
.setDescription("Send leadership evaluation reminder DMs (staff only)")
.addStringOption((option) =>
option
.setName("mission")
.setDescription("Mission ID, name, or code name. Omit to sweep all pending missions."),
) as SlashCommandBuilder,
execute: async (interaction, payload) => {
if (!interaction.inGuild() || !interaction.guild) {
await interaction.reply({
content: "This command only works in a server.",
flags: MessageFlags.Ephemeral,
});
return;
}
const member = await interaction.guild.members.fetch(interaction.user.id);
if (!(await isStaff(member, payload))) {
await interaction.reply({
content: "You don't have permission to trigger evaluation reminders.",
flags: MessageFlags.Ephemeral,
});
return;
}
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
const client = interaction.client as Client;
const missionInput = interaction.options.getString("mission")?.trim();
try {
if (missionInput) {
const { mission, error } = await resolveTargetMissions(payload, missionInput);
if (error || !mission) {
await interaction.editReply(error ?? `No mission matches "${missionInput}".`);
return;
}
if (
!isMissionEvaluable(
mission.ownershipAndStatus?.status ?? "",
mission.classification?.startDateTime ?? null,
)
) {
await interaction.editReply(
`**${missionLabel(mission)}** isn't evaluable yet — missions must be completed or past their scheduled start before reminders go out.`,
);
return;
}
const sent = await processMission(client, payload, mission);
await interaction.editReply(
`Done — ${sent} evaluation reminder DM(s) sent for **${missionLabel(mission)}**.`,
);
return;
}
const { processed, sent } = await reconcile(client, payload);
await interaction.editReply(
`Sweep complete — ${processed} pending mission(s) processed, ${sent} reminder DM(s) sent.`,
);
} catch (error) {
await interaction.editReply(
`Reminder trigger failed: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
},
};