At 8 PM Eastern on Wednesday, if no non-cancelled mission starts on that week's upcoming Friday, post once in #side-ops pinging the Community Zeus role (DISCORD_COMMUNITY_ZEUS_ROLE_ID, or the role resolved by name): the slot is open, hosting is welcome, and otherwise the slot stays empty or Jason hosts it himself. Dedup uses a stable per-Friday timestamp token scanned from recent channel messages, so no DB state. When a non-superuser later claims the slot the notice is edited to credit them; a cancelled or deleted claim reverts the notice, and a between-polls claim swap re-credits the new claimant.
61 lines
2 KiB
TypeScript
61 lines
2 KiB
TypeScript
export interface BotConfig {
|
|
token: string;
|
|
guildId: string;
|
|
appUrl: string;
|
|
opsChannelId: string | null;
|
|
announceChannelId: string | null;
|
|
staffRoleIds: string[];
|
|
sideOpsChannelId: string | null;
|
|
communityZeusRoleId: string | null;
|
|
attendancePollMs: number;
|
|
notificationPollMs: number;
|
|
evaluationPollMs: number;
|
|
readyReminderPollMs: number;
|
|
imminentReminderPollMs: number;
|
|
sideOpsNoticePollMs: number;
|
|
}
|
|
|
|
const required = (name: string): string => {
|
|
const value = process.env[name]?.trim();
|
|
if (!value) {
|
|
throw new Error(`[Bot] Missing required environment variable: ${name}`);
|
|
}
|
|
return value;
|
|
};
|
|
|
|
const optional = (name: string): string | null => {
|
|
const value = process.env[name]?.trim();
|
|
return value ? value : null;
|
|
};
|
|
|
|
const pollMs = (name: string, fallback: number): number => {
|
|
const raw = process.env[name]?.trim();
|
|
if (!raw) {
|
|
return fallback;
|
|
}
|
|
const value = Number.parseInt(raw, 10);
|
|
if (!Number.isInteger(value) || value <= 0) {
|
|
throw new Error(`[Bot] Invalid ${name}: expected a positive integer, got "${raw}"`);
|
|
}
|
|
return value;
|
|
};
|
|
|
|
export const botConfig: BotConfig = {
|
|
token: required("DISCORD_TOKEN"),
|
|
guildId: required("DISCORD_GUILD_ID"),
|
|
appUrl: required("APP_URL"),
|
|
opsChannelId: optional("DISCORD_OPS_CHANNEL_ID"),
|
|
announceChannelId: optional("DISCORD_ANNOUNCE_CHANNEL_ID"),
|
|
staffRoleIds: (optional("DISCORD_STAFF_ROLE_IDS") ?? "")
|
|
.split(",")
|
|
.map((id) => id.trim())
|
|
.filter((id) => id.length > 0),
|
|
sideOpsChannelId: optional("DISCORD_SIDE_OPS_CHANNEL_ID"),
|
|
communityZeusRoleId: optional("DISCORD_COMMUNITY_ZEUS_ROLE_ID"),
|
|
attendancePollMs: pollMs("DISCORD_ATTENDANCE_POLL_MS", 60_000),
|
|
notificationPollMs: pollMs("DISCORD_NOTIFICATION_POLL_MS", 20_000),
|
|
evaluationPollMs: pollMs("DISCORD_EVALUATION_POLL_MS", 60_000),
|
|
readyReminderPollMs: pollMs("DISCORD_READY_REMINDER_POLL_MS", 60_000),
|
|
imminentReminderPollMs: pollMs("DISCORD_IMMINENT_POLL_MS", 60_000),
|
|
sideOpsNoticePollMs: pollMs("DISCORD_SIDE_OPS_POLL_MS", 1_800_000),
|
|
};
|