Skip to content

Commit

Permalink
Reorg'd commands, moved ending week to a command-based process rather…
Browse files Browse the repository at this point in the history
… than an automated one
  • Loading branch information
SaltyQuetzals committed Apr 30, 2024
1 parent a93c3b0 commit 9136f33
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 143 deletions.
77 changes: 50 additions & 27 deletions src/commands/rmfp.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,72 @@
import { SlashCommandBuilder, SlashCommandSubcommandGroupBuilder } from 'discord.js';
import endSeason from './subcommands/endSeason.js';
import extend from './subcommands/extend.js';
import type { SubCommand } from './subcommands/index.js';
import leaderboard from './subcommands/leaderboard.js';
import startSeason from './subcommands/start/season.js';
import startWeek from './subcommands/start/week.js';
import endSeason from './subcommands/season/end.js';
import startSeason from './subcommands/season/start.js';
import endWeek from './subcommands/week/end.js';
import extend from './subcommands/week/extend.js';
import startWeek from './subcommands/week/start.js';
import type { Command } from './index.js';

const seasonCommands = new Map<string, SubCommand>([
[startSeason.name, startSeason],
[endSeason.name, endSeason],
]);

const weekCommands = new Map<string, SubCommand>([
[startWeek.name, startWeek],
[endWeek.name, endWeek],
[extend.name, extend],
]);

const miscCommands = new Map<string, SubCommand>([[leaderboard.name, leaderboard]]);

export default {
data: new SlashCommandBuilder()
.setName('rmfp')
.setDescription('Perform various RMFP-related tasks.')
.addSubcommandGroup(
new SlashCommandSubcommandGroupBuilder()
.setName('start')
.setDescription('Start a new RMFP session')
.setName('season')
.setDescription('Controls an RMFP season.')
.addSubcommand(startSeason.subCommandOption)
.addSubcommand(startWeek.subCommandOption),
.addSubcommand(endSeason.subCommandOption),
)
.addSubcommandGroup(
new SlashCommandSubcommandGroupBuilder()
.setName('week')
.setDescription('Controls an RMFP week.')
.addSubcommand(startWeek.subCommandOption)
.addSubcommand(extend.subCommandOption)
.addSubcommand(endWeek.subCommandOption),
)
.addSubcommand(endSeason.subCommandOption)
.addSubcommand(extend.subCommandOption)
.addSubcommand(leaderboard.subCommandOption)
.toJSON(),
async execute(interaction) {
if (!interaction.isChatInputCommand()) {
return;
}

switch (interaction.options.getSubcommand()) {
case startSeason.name:
await startSeason.execute(interaction);
break;
case startWeek.name:
await startWeek.execute(interaction);
break;
case endSeason.name:
await endSeason.execute(interaction);
break;
case leaderboard.name:
await leaderboard.execute(interaction);
break;
case extend.name:
await extend.execute(interaction);
break;
default:
break;
if (interaction.options.getSubcommandGroup() === null) {
const subCommand = miscCommands.get(interaction.options.getSubcommand());
await subCommand?.execute(interaction);
return;
}

let subCommand: SubCommand | undefined;
if (interaction.options.getSubcommandGroup() === 'season') {
subCommand = seasonCommands.get(interaction.options.getSubcommand());
} else {
subCommand = weekCommands.get(interaction.options.getSubcommand());
}

if (subCommand === undefined) {
console.error(
`Could not find a subcommand matching for "${interaction.options.getSubcommandGroup()} ${interaction.options.getSubcommand()}"`,
);
return;
}

await subCommand.execute(interaction);
},
} satisfies Command;
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { ButtonBuilder, ButtonStyle, ActionRowBuilder, GuildScheduledEventStatus } from 'discord.js';
import { isRMFPOwner, isRMFPOwnerFilter } from '../../common/isRMFPOwner.js';
import { prisma } from '../../common/prisma.js';
import type { SubCommand } from './index.js';
import { isRMFPOwner, isRMFPOwnerFilter } from '../../../common/isRMFPOwner.js';
import { prisma } from '../../../common/prisma.js';
import type { SubCommand } from '../index.js';

/**
* Ends the current RMFP season.
*/
export default {
subCommandOption: (subCommand) => subCommand.setName('end_season').setDescription('Ends this season of RMFP.'),
name: 'end_season',
subCommandOption: (subCommand) => subCommand.setName('end').setDescription('Ends this season of RMFP.'),
name: 'end',
async execute(interaction) {
if (!isRMFPOwner(interaction.guild, interaction.member)) {
await interaction.reply({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import type { SubCommand } from '../index.js';
* Begins a new RMFP season. Informs the user if one is ongoing that they must invoke `/rmfp end_season` first.
*/
export default {
subCommandOption: (subCommand) => subCommand.setName('season').setDescription('Starts a new season of RMFP!'),
name: 'season',
subCommandOption: (subCommand) => subCommand.setName('start').setDescription('Starts a new season of RMFP!'),
name: 'start',
async execute(interaction) {
if (!isRMFPOwner(interaction.guild, interaction.member)) {
await interaction.reply({
Expand Down
81 changes: 81 additions & 0 deletions src/commands/subcommands/week/end.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import process from 'node:process';
import type { Week } from '@prisma/client';
import type { Client } from 'discord.js';
import { isRMFPOwner } from '../../../common/isRMFPOwner.js';
import { prisma } from '../../../common/prisma.js';
import type { SubCommand } from '../index.js';

const closeRMFPWeek = async (week: Week, client: Client) => {
const guild = await client.guilds.fetch(process.env.GUILD_ID!);
const rmfpOwnerRole = guild.roles.cache.get(process.env.RMFP_OWNER_ROLE_ID!)!;
if (rmfpOwnerRole === null) {
console.error(`[Close RMFP Week] No RMFP owner role was detected!`);
return;
}

const rmfpOwners = rmfpOwnerRole.members.values();

console.log(`[Close RMFP Week] RMFP Owners found: ${rmfpOwnerRole.members.size}`);

const winners = await prisma.week.winners(week.number);
const content = [
`The winner(s) of RMFP S${week.seasonNumber}W${week.number} are:`,
...winners.map((winner, idx) => `${idx + 1}. <@${winner.userId}>'s [message](${winner.messageUrl})`),
].join('\n');

for (const owner of rmfpOwners) {
console.log(`Dispatching announcement message to: ${owner.user.username}`);
await owner.send(content);
}

console.log('[Close RMFP Week] Marking week as ended...');
await prisma.week.update({
where: {
id: week.id,
},
data: {
ended: true,
},
});
console.log('[Close RMFP Week] Week has been ended.');
};

/**
* Starts a new RMFP week with the provided theme. Informs the user that they must end an ongoing week, if there is one.
*/
export default {
subCommandOption: (subCommand) => subCommand.setName('end').setDescription('Ends the active week of RMFP.'),
name: 'end',
async execute(interaction) {
if (!isRMFPOwner(interaction.guild, interaction.member)) {
await interaction.reply({
content: 'Only the owner of RMFP may end the week.',
ephemeral: true,
});
return;
}

const currentSeason = await prisma.season.current();

if (currentSeason === null) {
await interaction.reply({
content:
"There's no RMFP season ongoing. You need to start a season (`/rmfp start season`) before running this command.",
ephemeral: true,
});
return;
}

const currentWeek = await prisma.week.current();
if (currentWeek === null) {
await interaction.reply({
content: "There's no RMFP week ongoing. This command will do nothing.",
ephemeral: true,
});
return;
}

await interaction.reply({ content: 'Ending the current week!', ephemeral: true });
await closeRMFPWeek(currentWeek, interaction.client);
},
} satisfies SubCommand;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Temporal } from '@js-temporal/polyfill';
import { isRMFPOwner } from '../../common/isRMFPOwner.js';
import { prisma } from '../../common/prisma.js';
import type { SubCommand } from './index.js';
import { isRMFPOwner } from '../../../common/isRMFPOwner.js';
import { prisma } from '../../../common/prisma.js';
import type { SubCommand } from '../index.js';

export default {
subCommandOption: (subCommand) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ const THEME_OPTION = 'theme';
export default {
subCommandOption: (subCommand) =>
subCommand
.setName('week')
.setName('start')
.setDescription('Starts a new week of RMFP.')
.addStringOption((option) =>
option.setName(THEME_OPTION).setDescription("What's this week's theme?").setRequired(true),
),
name: 'week',
name: 'start',
async execute(interaction) {
if (!isRMFPOwner(interaction.guild, interaction.member)) {
await interaction.reply({
Expand Down
4 changes: 1 addition & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import process from 'node:process';
import { URL } from 'node:url';
import { Client, GatewayIntentBits, Partials } from 'discord.js';
import { loadCommands, loadEvents, loadJobs } from './util/loaders.js';
import { loadCommands, loadEvents } from './util/loaders.js';
import { registerEvents } from './util/registerEvents.js';

// Initialize the client
Expand All @@ -18,10 +18,8 @@ const client = new Client({
// Load the events and commands
const eventsUrl = new URL('events/', import.meta.url);
const commandsUrl = new URL('commands/', import.meta.url);
const jobsUrl = new URL('jobs/', import.meta.url);
const events = await loadEvents(eventsUrl);
const commands = await loadCommands(commandsUrl);
await loadJobs(jobsUrl);
// Register the event handlers
registerEvents(commands, events, client);

Expand Down
16 changes: 0 additions & 16 deletions src/jobs/index.ts

This file was deleted.

75 changes: 0 additions & 75 deletions src/jobs/pollWeek.ts

This file was deleted.

10 changes: 0 additions & 10 deletions src/util/loaders.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import type { PathLike } from 'node:fs';
import { readdir, stat } from 'node:fs/promises';
import { URL } from 'node:url';
import { CronJob } from 'cron';
import { predicate as commandPredicate } from '../commands/index.js';
import type { Command } from '../commands/index.ts';
import { predicate as eventPredicate } from '../events/index.js';
import type { Event } from '../events/index.js';
import type { Job } from '../jobs/index.js';
import { predicate as jobPredicate } from '../jobs/index.js';

/**
* A predicate to check if the structure is valid
Expand Down Expand Up @@ -77,10 +74,3 @@ export async function loadCommands(dir: PathLike, recursive = true): Promise<Map
export async function loadEvents(dir: PathLike, recursive = true): Promise<Event[]> {
return loadStructures(dir, eventPredicate, recursive);
}

export async function loadJobs(dir: PathLike, recursive = true): Promise<CronJob[]> {
const jobs = await loadStructures(dir, jobPredicate, recursive);
return jobs.map(
(jobParams) => new CronJob(jobParams.cronTime, jobParams.onTick, undefined, jobParams.start, jobParams.timeZone),
);
}

0 comments on commit 9136f33

Please sign in to comment.