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.3
1.0.4
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ The bot requires a handful of permissions to be granted when added to a server.
- **Send Messages**: Required to reply to commands with a message
- **Send Messages in Threads**: Required to send messages in threads
- **Create Private Threads**: Required to create new private threads
- **Manage Threads**: Required to add/remove users from a thread, pin messages in threads, and close and archive threads
- **Manage Threads**: Required to add/remove users from a thread and close and archive threads
- **Mention @€veryone, @here, and All Roles**: Required to ping the casual and competitive roles
- **Move Members**: Required to disconnect users from voice channels if kicked or dropped out

## Running locally
Expand Down
2 changes: 1 addition & 1 deletion src/client/discord-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function createDiscordClient() {
GatewayIntentBits.MessageContent,
GatewayIntentBits.DirectMessages,
],
allowedMentions: { parse: ['roles'] },
allowedMentions: { parse: ['roles', 'users', 'everyone'] },
partials: [Partials.Channel],
});
}
Expand Down
1 change: 1 addition & 0 deletions src/commands/create-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export async function handleCreateCommand(
content: rolePing || undefined,
embeds: [embed],
components: [buttonRow, selectRow],
allowedMentions: { parse: ['roles', 'users', 'everyone'] },
});
const message = await reply.fetch();
const matchId = randomUUID();
Expand Down
26 changes: 17 additions & 9 deletions src/commands/reping-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ export async function handleRepingCommand(
telemetry?: TelemetryService,
) {
try {
await interaction.deferReply();

const userId = interaction.user.id;
const userEventId = eventManager.userOwnsEvent(userId);

if (!userEventId) {
await interaction.editReply({
await interaction.reply({
content: ERROR_MESSAGES.NO_EVENT_OWNED,
flags: ['Ephemeral'],
});
return;
}
Expand All @@ -51,17 +50,19 @@ export async function handleRepingCommand(
const remainingMs = TIMINGS.REPING_COOLDOWN_MS - timeSinceLastUse;
const remainingMinutes = Math.ceil(remainingMs / 60000);

await interaction.editReply({
await interaction.reply({
content: ERROR_MESSAGES.REPING_COOLDOWN(remainingMinutes),
flags: ['Ephemeral'],
});
return;
}
}

const channelId = eventManager.getChannelId(userEventId);
if (!channelId) {
await interaction.editReply({
await interaction.reply({
content: ERROR_MESSAGES.CHANNEL_NOT_FOUND,
flags: ['Ephemeral'],
});
return;
}
Expand All @@ -72,8 +73,9 @@ export async function handleRepingCommand(
);

if (!channel || !channel.isTextBased()) {
await interaction.editReply({
await interaction.reply({
content: ERROR_MESSAGES.CHANNEL_NO_ACCESS,
flags: ['Ephemeral'],
});
return;
}
Expand All @@ -84,8 +86,9 @@ export async function handleRepingCommand(
);

if (!message || !message.embeds[0]) {
await interaction.editReply({
await interaction.reply({
content: ERROR_MESSAGES.MESSAGE_NOT_FOUND,
flags: ['Ephemeral'],
});
return;
}
Expand All @@ -98,20 +101,24 @@ export async function handleRepingCommand(
const missingPlayers = MAX_PARTICIPANTS - currentCount;

if (currentCount === MAX_PARTICIPANTS) {
await interaction.editReply({
await interaction.reply({
content: ERROR_MESSAGES.REPING_EVENT_FULL,
flags: ['Ephemeral'],
});
return;
}

const rolePing = getPingsForServer(interaction, isCasual);
if (!rolePing) {
await interaction.editReply({
await interaction.reply({
content: ERROR_MESSAGES.ROLE_NOT_FOUND,
flags: ['Ephemeral'],
});
return;
}

await interaction.deferReply();

await eventManager.deleteRepingMessageIfExists(
userEventId,
interaction.client,
Expand All @@ -122,6 +129,7 @@ export async function handleRepingCommand(

const reply = await interaction.editReply({
content: REPING_MESSAGE(rolePing, missingPlayers, messageUrl),
allowedMentions: { parse: ['roles', 'users', 'everyone'] },
});

const repingMessage = await withRetry(
Expand Down
21 changes: 17 additions & 4 deletions src/tests/commands/reping-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe('reping-command', () => {
fetch: vi.fn().mockResolvedValue(mockChannel),
},
},
reply: vi.fn().mockResolvedValue(undefined),
deferReply: vi.fn().mockResolvedValue(undefined),
editReply: vi.fn().mockResolvedValue(mockReply),
};
Expand Down Expand Up @@ -102,6 +103,8 @@ describe('reping-command', () => {
});

it('should reject if user does not own event', async () => {
const mockReply = vi.fn().mockResolvedValue(undefined);
mockInteraction.reply = mockReply;
mockEventManager.userOwnsEvent.mockReturnValue(false as never);

await handleRepingCommand(
Expand All @@ -110,12 +113,15 @@ describe('reping-command', () => {
mockTelemetry as never,
);

expect(mockInteraction.editReply).toHaveBeenCalledWith({
expect(mockReply).toHaveBeenCalledWith({
content: ERROR_MESSAGES.NO_EVENT_OWNED,
flags: ['Ephemeral'],
});
});

it('should enforce cooldown period', async () => {
const mockReply = vi.fn().mockResolvedValue(undefined);
mockInteraction.reply = mockReply;
const now = Date.now();
mockEventManager.getRepingCooldown.mockReturnValue(
now - TIMINGS.REPING_COOLDOWN_MS / 2,
Expand All @@ -127,14 +133,17 @@ describe('reping-command', () => {
mockTelemetry as never,
);

expect(mockInteraction.editReply).toHaveBeenCalledWith(
expect(mockReply).toHaveBeenCalledWith(
expect.objectContaining({
content: expect.stringContaining('wait'),
flags: ['Ephemeral'],
}),
);
});

it('should reject if event is full', async () => {
const mockReply = vi.fn().mockResolvedValue(undefined);
mockInteraction.reply = mockReply;
const participants = new Map([
['user1', { userId: 'user1', role: 'None', rank: null }],
['user2', { userId: 'user2', role: 'Tank', rank: null }],
Expand All @@ -153,8 +162,9 @@ describe('reping-command', () => {
mockTelemetry as never,
);

expect(mockInteraction.editReply).toHaveBeenCalledWith({
expect(mockReply).toHaveBeenCalledWith({
content: ERROR_MESSAGES.REPING_EVENT_FULL,
flags: ['Ephemeral'],
});
});

Expand Down Expand Up @@ -187,6 +197,8 @@ describe('reping-command', () => {
});

it('should reject if channel not found', async () => {
const mockReply = vi.fn().mockResolvedValue(undefined);
mockInteraction.reply = mockReply;
mockEventManager.getChannelId.mockReturnValue(undefined as never);

await handleRepingCommand(
Expand All @@ -195,8 +207,9 @@ describe('reping-command', () => {
mockTelemetry as never,
);

expect(mockInteraction.editReply).toHaveBeenCalledWith({
expect(mockReply).toHaveBeenCalledWith({
content: ERROR_MESSAGES.CHANNEL_NOT_FOUND,
flags: ['Ephemeral'],
});
});

Expand Down