Five days before a player-created side op starts, if it is not yet Ready, the creator gets one in-app notification per day (via mission-tick) plus one Discord DM per day (new bot service). Per-server-local-day dedupe via hidden readyReminderWebSentAt/readyReminderDmSentAt markers. The ready-reminder type is excluded from the notification bridge to avoid double-DMing. Markers are nulled on clone.
53 lines
1.6 KiB
TypeScript
53 lines
1.6 KiB
TypeScript
export interface BotConfig {
|
|
token: string;
|
|
guildId: string;
|
|
appUrl: string;
|
|
opsChannelId: string | null;
|
|
announceChannelId: string | null;
|
|
staffRoleIds: string[];
|
|
attendancePollMs: number;
|
|
notificationPollMs: number;
|
|
evaluationPollMs: number;
|
|
readyReminderPollMs: 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),
|
|
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),
|
|
};
|