- ATK/DEF/Heal now accept K/M shorthand (500K, 1.4M, case-insensitive) via
a shared parser (@helpers/stat-value), used identically by the Submit
Score modal, /tg score set, and /tg-admin score-inject
- /tg-admin score-modal — officers can open the Submit Score modal on
behalf of any player; the modal's customId now carries the target
userKey so submission always resolves to the intended player rather
than whoever's Discord client is submitting it
- fix: /tg call now actually reveals the Submit Score button (was
recording the call but never opening submission)
- fix: /tg poll reload no longer resurrects Submit Score / Yes-No buttons
that had already been removed — poll state now tracks submission-open
and buttons-removed explicitly instead of re-deriving it from
locked/confirmed on every render
- fix: midnight cleanup now fully removes all poll buttons (not just
Submit Score) instead of leaving them disabled
- Sleep Check — players on a configured role get flagged (💤 shown on
their poll row) the moment they vote Yes; a scheduled per-slot job DMs
everyone currently flagged at a configurable time before TG (default
20 min). State changes and DM sending are intentionally decoupled —
flagging never itself sends a message. Officers can also flag/clear
manually via /tg-admin sleep-check set|clear
- data/updates/v0.9.2 and new v0.10 changelog entries for the above
- merged the two .claude reference docs into one, removed stale/fixed
items, documented the new systems and known deferred gaps
43 lines
No EOL
1.6 KiB
TypeScript
43 lines
No EOL
1.6 KiB
TypeScript
import { ChatInputCommandInteraction, TextChannel } from "discord.js";
|
|
import { Config } from "@systems/config";
|
|
import { polls, updatePollMessage } from "@systems/poll";
|
|
import { persist } from "@systems/pollPersistence";
|
|
import { hasOfficerRole } from "@systems/users";
|
|
import { Discord } from "@discord";
|
|
import { replyAndDelete } from "@utils";
|
|
|
|
export async function handleCall(interaction: ChatInputCommandInteraction): Promise<void> {
|
|
const member = await interaction.guild!.members.fetch(interaction.user.id);
|
|
const callRoles = Config.get({ section: "roles", key: "callGame" });
|
|
|
|
if (!hasOfficerRole(member, callRoles)) {
|
|
return void replyAndDelete(interaction, "❌ You don't have permission to call TG.", true);
|
|
}
|
|
|
|
const opts = Discord.Interaction.options(interaction);
|
|
const slot = opts.integer({ key: "slot", required: true })!;
|
|
const state = polls.get(slot);
|
|
|
|
if (!state) {
|
|
return void replyAndDelete(interaction, `❌ No active poll for ${slot}:00.`, true);
|
|
}
|
|
if (!state.locked) {
|
|
return void replyAndDelete(interaction, "❌ Poll must be locked before calling TG.", true);
|
|
}
|
|
|
|
state.called = true;
|
|
state.calledAt = new Date().toISOString();
|
|
// Calling TG ends it immediately — open score submission the same way the
|
|
// scheduled tgHour+closesAfter close normally does.
|
|
state.scoreSubmitOpen = true;
|
|
persist.save(polls);
|
|
|
|
const channel = interaction.channel as TextChannel;
|
|
await updatePollMessage(channel, slot, undefined, true);
|
|
|
|
return void replyAndDelete(interaction, `✅ TG at ${slot}:00 has been called — Submit Score is now open.`, true);
|
|
}
|
|
|
|
export const CallCommands = {
|
|
call: handleCall,
|
|
}; |