Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.7
1.0.8
40 changes: 3 additions & 37 deletions src/client/shutdown.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import type { Client } from 'discord.js';
import { EmbedBuilder } from 'discord.js';
import {
AUTHOR_ID,
COLORS,
FIELD_NAMES,
STATUS_MESSAGES,
TIMESTAMP,
TIMINGS,
} from '../constants.js';
import { AUTHOR_ID, TIMESTAMP, TIMINGS } from '../constants.js';
import { cleanupEvent } from '../event/event-lifecycle.js';
import type { EventManager } from '../event/event-manager.js';
import type { ThreadManager } from '../managers/thread-manager.js';
import type { VoiceChannelManager } from '../managers/voice-channel-manager.js';
import { stopMetricsServer } from '../telemetry/metrics.js';
import type { TelemetryService } from '../telemetry/telemetry.js';
import { updateEmbedField } from '../utils/embed-utils.js';
import { ErrorSeverity, handleError } from '../utils/error-handler.js';
import { MEDIUM_RETRY_OPTIONS, withRetryOrNull } from '../utils/retry.js';

Expand Down Expand Up @@ -78,33 +69,8 @@ export async function gracefulShutdown(
`Updating event message ${i + 1}/${allTimers.length}: ${eventId}`,
);
if (channelId) {
const channel = await withRetryOrNull(
() => client.channels.fetch(channelId),
MEDIUM_RETRY_OPTIONS,
);

if (channel?.isTextBased() && !channel.isDMBased()) {
const message = await withRetryOrNull(
() => channel.messages.fetch(eventId),
MEDIUM_RETRY_OPTIONS,
);

if (message) {
const embed = EmbedBuilder.from(message.embeds[0]).setColor(
COLORS.CANCELLED,
);

updateEmbedField(
embed,
FIELD_NAMES.STATUS,
STATUS_MESSAGES.SHUTDOWN,
);
await withRetryOrNull(
() => message.edit({ embeds: [embed], components: [] }),
MEDIUM_RETRY_OPTIONS,
);
}
}
eventManager.setTerminalState(eventId, 'shutdown');
await eventManager.queueUpdate(eventId, true);
}

console.log(`Cleaning up event ${i + 1}/${allTimers.length}: ${eventId}`);
Expand Down
20 changes: 11 additions & 9 deletions src/commands/create-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ export async function handleCreateCommand(
const buttonRow = createEventButtons(timeInMinutes);
const selectRow = createRoleSelectMenu();

const rankId = getExcaliburRankOfUser(
interaction.guild?.id,
interaction.member as GuildMember,
);

const embed = createEventEmbed(
interaction.guild?.id,
rankId,
interaction.user.username,
interaction.user.displayAvatarURL(),
interaction.user.id,
Expand All @@ -65,11 +72,9 @@ export async function handleCreateCommand(
eventManager.setCreator(message.id, interaction.user.id);
eventManager.setMatchId(message.id, matchId);
eventManager.setChannelId(message.id, message.channelId);
await eventManager.removeUserFromAllQueues(
interaction.user.id,
interaction.client,
telemetry,
);
eventManager.setMessageData(message.id, casual, info);

await eventManager.removeUserFromAllQueues(interaction.user.id, telemetry);

eventManager.setTimer(message.id, {
startTime,
Expand All @@ -87,10 +92,7 @@ export async function handleCreateCommand(
{
userId: interaction.user.id,
role: WEAPON_ROLES[0],
rank: getExcaliburRankOfUser(
interaction.guild?.id,
interaction.member as GuildMember,
),
rank: rankId,
},
],
]),
Expand Down
19 changes: 2 additions & 17 deletions src/commands/kick-command.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import {
type ChatInputCommandInteraction,
EmbedBuilder,
type TextChannel,
} from 'discord.js';
import type { ChatInputCommandInteraction, TextChannel } from 'discord.js';
import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants.js';
import { promoteNextFromQueue } from '../event/event-lifecycle.js';
import type { EventManager } from '../event/event-manager.js';
import type { ThreadManager } from '../managers/thread-manager.js';
import type { VoiceChannelManager } from '../managers/voice-channel-manager.js';
import type { TelemetryService } from '../telemetry/telemetry.js';
import {
updateParticipantFields,
updateQueueField,
} from '../utils/embed-utils.js';
import { ErrorSeverity, handleError } from '../utils/error-handler.js';
import {
checkProcessingStates,
Expand Down Expand Up @@ -134,14 +126,7 @@ export async function handleKickCommand(
const updatedParticipants = eventManager.getParticipants(userEventId);

if (timerData && updatedParticipants) {
const embed = EmbedBuilder.from(message.embeds[0]);

updateParticipantFields(embed, updatedParticipants);

const queue = eventManager.getQueue(userEventId);
updateQueueField(embed, queue);

await message.edit({ embeds: [embed] });
eventManager.queueUpdate(userEventId);
}

telemetry?.trackUserKicked({
Expand Down
14 changes: 13 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ export const TIMINGS = {
HOUR_IN_MS: 60 * 60 * 1000,
DAY_IN_MS: 24 * 60 * 60 * 1000,
PROCESSING_TIMEOUT_MS: 30000,
EVENT_START_DELAY_MINUTES: DEV ? 0 : 0,
SHUTDOWN_EVENT_CLEANUP_DELAY_MS: 2000,
REPING_COOLDOWN_MS: 15 * 60 * 1000,
MESSAGE_UPDATE_DEBOUNCE_MS: 300,
} as const;

export const TIME_UNITS = {
Expand Down Expand Up @@ -188,26 +188,38 @@ export const EXCALIBUR_RANKS = {
'1': {
name: 'TX Grandmaster',
id: '1429217994168598669',
emoteName: 'Ex8s1_grandmaster',
emoteId: '1429215523824078941',
},
'2': {
name: 'T1 Legend',
id: '1428998361188532264',
emoteName: 'Ex8s2_legend',
emoteId: '1428988098892529787',
},
'3': {
name: 'T2 Ascendant',
id: '1428997469303341166',
emoteName: 'Ex8s3_ascendant',
emoteId: '1428988084535427102',
},
'4': {
name: 'T3 Elite',
id: '1428997715106332815',
emoteName: 'Ex8s4_elite',
emoteId: '1428988071059259392',
},
'5': {
name: 'T4 Knight',
id: '1428998081126596618',
emoteName: 'Ex8s5_knight',
emoteId: '1428988053472415768',
},
'6': {
name: 'T5 Squire',
id: '1428998419250286704',
emoteName: 'Ex8s6_novice',
emoteId: '1428988037383327825',
},
} as const;

Expand Down
125 changes: 26 additions & 99 deletions src/event/event-lifecycle.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,20 @@
import type { Client, Guild, Message, TextChannel } from 'discord.js';
import { EmbedBuilder } from 'discord.js';
import {
type Client,
EmbedBuilder,
type Guild,
type Message,
type TextChannel,
} from 'discord.js';
import {
COLORS,
FIELD_NAMES,
MATCH_ID_LENGTH,
MAX_PARTICIPANTS,
START_MESSAGES,
STATUS_MESSAGES,
TIMINGS,
WEAPON_ROLES,
} from '../constants.js';
import type { ThreadManager } from '../managers/thread-manager.js';
import type { VoiceChannelManager } from '../managers/voice-channel-manager.js';
import type { TelemetryService } from '../telemetry/telemetry.js';
import {
createEventStartedButtons,
createRoleSelectMenu,
updateEmbedField,
} from '../utils/embed-utils.js';
import { ErrorSeverity, handleError } from '../utils/error-handler.js';
import {
checkProcessingStates,
getExcaliburRankOfUser,
} from '../utils/helpers.js';
import {
LOW_RETRY_OPTIONS,
MEDIUM_RETRY_OPTIONS,
withRetry,
withRetryOrNull,
} from '../utils/retry.js';
import { LOW_RETRY_OPTIONS, withRetryOrNull } from '../utils/retry.js';
import type { EventManager, ParticipantMap } from './event-manager.js';

export async function startEvent(
Expand Down Expand Up @@ -62,23 +43,7 @@ export async function startEvent(
eventManager.deleteTimeout(message.id);
}

const embed = EmbedBuilder.from(message.embeds[0]).setColor(COLORS.STARTED);

updateEmbedField(embed, FIELD_NAMES.STATUS, STATUS_MESSAGES.STARTED);
updateEmbedField(
embed,
FIELD_NAMES.START,
START_MESSAGES.AT_TIME(Date.now()),
);

const buttonRow = createEventStartedButtons();
const selectRow = createRoleSelectMenu();

await withRetry(
() =>
message.edit({ embeds: [embed], components: [buttonRow, selectRow] }),
MEDIUM_RETRY_OPTIONS,
);
eventManager.queueUpdate(message.id);

const participants = Array.from(participantMap.values());
const channel = message.channel as TextChannel;
Expand Down Expand Up @@ -214,53 +179,20 @@ export async function cleanupStaleEvents(
const channelId = eventManager.getChannelId(messageId);
const guildId = eventManager.getGuildId(messageId);

let message: Message | null = null;

if (channelId) {
try {
const channel = await withRetryOrNull(
() => appClient.channels.fetch(channelId),
LOW_RETRY_OPTIONS,
);
eventManager.setTerminalState(messageId, 'expired');
await eventManager.queueUpdate(messageId, true);

if (channel?.isTextBased() && !channel.isDMBased()) {
message = await withRetryOrNull(
() => channel.messages.fetch(messageId),
LOW_RETRY_OPTIONS,
);
}
} catch (error) {
handleError({
reason: 'Failed to fetch stale event message',
severity: ErrorSeverity.LOW,
error,
metadata: { messageId, channelId },
});
}
}
if (message) {
const embed = EmbedBuilder.from(message.embeds[0]).setColor(
COLORS.CANCELLED,
);
updateEmbedField(embed, FIELD_NAMES.STATUS, STATUS_MESSAGES.EXPIRED);
const matchId = eventManager.getMatchId(messageId);
const participants = eventManager.getParticipants(messageId);

await withRetryOrNull(
() => message.edit({ embeds: [embed], components: [] }),
LOW_RETRY_OPTIONS,
);

const matchId = eventManager.getMatchId(messageId);
const participants = eventManager.getParticipants(messageId);

telemetry?.trackEventExpired({
guildId: guildId || message.guild?.id || 'unknown',
eventId: messageId,
userId: appClient.user?.id || 'unknown',
participants: Array.from((participants || new Map()).values()),
channelId: message.channelId,
matchId: matchId || 'unknown',
});
}
telemetry?.trackEventExpired({
guildId: guildId || 'unknown',
eventId: messageId,
userId: appClient.user?.id || 'unknown',
participants: Array.from((participants || new Map()).values()),
channelId: channelId || 'unknown',
matchId: matchId || 'unknown',
});
} catch (error) {
handleError({
reason: `Failed to process stale event ${messageId}`,
Expand Down Expand Up @@ -294,13 +226,11 @@ export async function createEventStartTimeout(
eventManager.deleteTimeout(message.id);
}

const embed = EmbedBuilder.from(message.embeds[0]);
updateEmbedField(
embed,
FIELD_NAMES.START,
START_MESSAGES.AT_TIME(Date.now() + timeInMinutes * TIMINGS.MINUTE_IN_MS),
);
await message.edit({ embeds: [embed] });
const timerData = eventManager.getTimer(message.id);
if (timerData) {
timerData.duration = timeInMinutes * TIMINGS.MINUTE_IN_MS;
eventManager.queueUpdate(message.id);
}

const timeout = setTimeout(async () => {
try {
Expand All @@ -326,13 +256,10 @@ export async function createEventStartTimeout(
telemetry,
);
} else {
const embed = EmbedBuilder.from(message.embeds[0]);
embed.setColor(COLORS.OPEN);

updateEmbedField(embed, FIELD_NAMES.START, START_MESSAGES.WHEN_FULL);
updateEmbedField(embed, FIELD_NAMES.STATUS, STATUS_MESSAGES.OPEN);

await message.edit({ embeds: [embed] });
if (timerData) {
timerData.duration = undefined;
}
eventManager.queueUpdate(message.id);
}
} catch (error) {
handleError({
Expand Down Expand Up @@ -373,7 +300,7 @@ export async function promoteNextFromQueue(
rank: getExcaliburRankOfUser(guild.id, member),
});

await eventManager.removeUserFromAllQueues(nextUserId, appClient, telemetry);
await eventManager.removeUserFromAllQueues(nextUserId, telemetry);

const threadId = eventManager.getThread(messageId);
if (threadId) {
Expand Down
Loading