tg-bot-ts/src/subcommands/poll/lock.ts
Nuno Duque Nunes fc4f322885 feature: shorthand stat input, admin score tools, and sleep check system
- 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
2026-07-30 04:16:42 +01:00

34 lines
No EOL
1.5 KiB
TypeScript

import { ChatInputCommandInteraction, TextChannel } from "discord.js";
import { Config } from "@systems/config";
import { polls, lockPoll, updatePollMessage } from "@systems/poll";
import { persist } from "@systems/pollPersistence";
import { replyAndDelete } from "@utils";
export async function handleLock(interaction: ChatInputCommandInteraction): Promise<void> {
const oneTimeMsg = interaction.options.getString("message") ?? undefined;
const simulateClose = interaction.options.getBoolean("simulate_close") ?? false;
const slot = getActiveSlot();
if (slot === undefined) return void replyAndDelete(interaction, "❌ No active poll found.");
// Use lockPoll() so lockedYesKeys gets snapshotted — same path as the cron
lockPoll(slot);
const channel = await interaction.client.channels.fetch(Config.get({ section: "channels", key: "poll" })) as TextChannel;
if (simulateClose) {
// Simulate TG end: show Submit Score button (same path as onPollClose cron)
const state = polls.get(slot)!;
state.scoreSubmitOpen = true;
persist.save(polls);
await updatePollMessage(channel, slot, oneTimeMsg, true);
return void replyAndDelete(interaction, "🔒 Poll locked + 📊 Submit Score button shown (simulate close).");
}
// Normal lock: voting closes, no submit button yet
await updatePollMessage(channel, slot, oneTimeMsg);
return void replyAndDelete(interaction, "🔒 Poll locked.");
}
function getActiveSlot(): number | undefined {
return [...polls.keys()][0];
}