From 669af5ad14f1b2bc84bf3300c9a7e234e0029c24 Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Sat, 22 Aug 2026 17:48:47 -0400 Subject: [PATCH] Improve service handling --- src/cmd/registry/chan.ts | 8 ++--- src/cmd/registry/comfy.ts | 6 ++-- src/cmd/registry/{mode.ts => limbomode.ts} | 6 ++-- src/cmd/registry/redpillme.ts | 2 +- src/cmd/registry/roleinit.ts | 10 +++++++ src/cmd/service.ts | 34 ---------------------- src/index.ts | 20 ++++++------- src/models.ts | 10 +++++++ src/{util => services}/chan.ts | 2 +- src/services/command.ts | 23 +++++++++++++++ src/services/role.ts | 18 ++++-------- 11 files changed, 70 insertions(+), 69 deletions(-) rename src/cmd/registry/{mode.ts => limbomode.ts} (74%) create mode 100644 src/cmd/registry/roleinit.ts delete mode 100644 src/cmd/service.ts create mode 100644 src/models.ts rename src/{util => services}/chan.ts (97%) create mode 100644 src/services/command.ts diff --git a/src/cmd/registry/chan.ts b/src/cmd/registry/chan.ts index a274492..edb65a7 100644 --- a/src/cmd/registry/chan.ts +++ b/src/cmd/registry/chan.ts @@ -1,6 +1,6 @@ import type { Message } from "discord.js"; -import type { Command } from "../service"; -import Chan from "../../util/chan"; +import ChanService from "../../services/chan"; +import type { Command } from "../../models"; export default { name: "chan", @@ -14,11 +14,11 @@ export default { return; }; - if (Chan.isNsfwBoard(board)) { + if (ChanService.isNsfwBoard(board)) { await message.reply("Fuck off nigger") return; }; - await message.reply(await Chan.getMediaLink(board, search)); + await message.reply(await ChanService.getMediaLink(board, search)); } } satisfies Command; diff --git a/src/cmd/registry/comfy.ts b/src/cmd/registry/comfy.ts index 7fbcc41..bac1b2d 100644 --- a/src/cmd/registry/comfy.ts +++ b/src/cmd/registry/comfy.ts @@ -1,6 +1,6 @@ import type { Message } from "discord.js"; -import type { Command } from "../service"; -import Chan from "../../util/chan"; +import ChanService from "../../services/chan"; +import type { Command } from "../../models"; export default { name: "comfy", @@ -9,6 +9,6 @@ export default { execute: async (message: Message) => { const boards = ["wsg", "wg"]; const board = boards[Math.floor(Math.random() * boards.length)] ?? "wsg"; - await message.reply(await Chan.getMediaLink(board, "comfy")); + await message.reply(await ChanService.getMediaLink(board, "comfy")); } } satisfies Command; diff --git a/src/cmd/registry/mode.ts b/src/cmd/registry/limbomode.ts similarity index 74% rename from src/cmd/registry/mode.ts rename to src/cmd/registry/limbomode.ts index 54bb254..7dd10e8 100644 --- a/src/cmd/registry/mode.ts +++ b/src/cmd/registry/limbomode.ts @@ -1,10 +1,10 @@ import type { Message } from "discord.js"; -import type { Command } from "../service"; +import type { Command } from "../../models"; export default { - name: "limbo", + name: "limbomode", description: "Toggles on/off limbo mode. When limbo mode is on, new members will be placed in #limbo. When off, new members will be placed in #chat-and-shitpost.", - usage: ".limbo [on|off]", + usage: ".limbomode [on|off]", execute: async (message: Message) => { } } satisfies Command; diff --git a/src/cmd/registry/redpillme.ts b/src/cmd/registry/redpillme.ts index b6c585f..07c475a 100644 --- a/src/cmd/registry/redpillme.ts +++ b/src/cmd/registry/redpillme.ts @@ -1,5 +1,5 @@ import type { Message } from "discord.js"; -import type { Command } from "../service"; +import type { Command } from "../../models"; export default { name: "redpillme", diff --git a/src/cmd/registry/roleinit.ts b/src/cmd/registry/roleinit.ts new file mode 100644 index 0000000..a9e6353 --- /dev/null +++ b/src/cmd/registry/roleinit.ts @@ -0,0 +1,10 @@ +import type { Message } from "discord.js"; +import type { Command } from "../../models"; + +export default { + name: "roleinit", + description: "Creates all of the required roles, if they do not exist yet.", + usage: ".roleinit", + execute: async (message: Message) => { + } +} satisfies Command; diff --git a/src/cmd/service.ts b/src/cmd/service.ts deleted file mode 100644 index 31a4dd1..0000000 --- a/src/cmd/service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Client, Message } from "discord.js"; -import { readdir } from "fs/promises"; - -const CMD_PREFIX = "."; - -export interface Command { - name: string, - description: string, - usage: string, - execute: (message: Message) => void | Promise -} - -export class CommandService { - private client: Client - private registry: Record = {} - - constructor(client: Client) { - this.client = client; - } - - async init() { - const commands = await Promise.all( - (await readdir(`${import.meta.dir}/registry`)).map(async file => (await import(`./registry/${file}`)).default as Command) - ); - for (const command of commands) - this.registry[command.name] = command; - }; - - parse(message: Message): Command | undefined { - if (!message.content.startsWith(CMD_PREFIX)) return; - const name = message.content.slice(CMD_PREFIX.length).split(/\s+/)[0]; - return this.registry[name ?? ""]; - } -} diff --git a/src/index.ts b/src/index.ts index 730ace3..71e319c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,36 +1,34 @@ import { Client, Events, GatewayIntentBits } from 'discord.js'; -import { CommandService } from './cmd/service'; import RoleService from './services/role'; +import CommandService from './services/command'; -const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] }); - -// Services -const commandService = new CommandService(client); -const roleService = new RoleService(client); +const client = new Client({ + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] +}); client.once(Events.ClientReady, async (readyClient) => { // Initialize & validate - await commandService.init(); - roleService.validate(client.guilds.cache); + await CommandService.init(); + RoleService.validate(client.guilds.cache); // Ready! console.log(`Ready! Logged in as ${readyClient.user.tag}`); }); client.on("guildMemberAdd", async (guildMember) => { - await roleService.giveStarterRoles(guildMember); + await RoleService.giveStarterRoles(guildMember); }); client.on("messageCreate", async (message) => { if (message.author.bot) return; - const command = commandService.parse(message); + const command = CommandService.parse(message); if (command) command.execute(message); }); Bun.cron("0 */6 * * *", async () => { for (const guild of client.guilds.cache.values()) { - const lurkerRole = roleService.getRoleByName(guild, RoleService.ROLES["LURKER"]?.name); + const lurkerRole = RoleService.getRoleByName(guild, RoleService.ROLES["LURKER"]?.name); if (!lurkerRole) continue; await guild.members.prune({ days: 5, roles: [lurkerRole] }); diff --git a/src/models.ts b/src/models.ts new file mode 100644 index 0000000..6344563 --- /dev/null +++ b/src/models.ts @@ -0,0 +1,10 @@ +import type { Message } from "discord.js"; + +// COMMANDS + +export interface Command { + name: string, + description: string, + usage: string, + execute: (message: Message) => void | Promise +} diff --git a/src/util/chan.ts b/src/services/chan.ts similarity index 97% rename from src/util/chan.ts rename to src/services/chan.ts index 6fe5d45..00dda5b 100644 --- a/src/util/chan.ts +++ b/src/services/chan.ts @@ -1,4 +1,4 @@ -export default class Chan { +export default abstract class ChanService { private static readonly nsfwBoards = new Set([ "b", "d", "e", "h", "hc", "hr", "gif", "s", "t", "u", "r9k", "soc", "vr", "w", "i", "ic", "r", "p", "cgl", "cm", "n", "vip", "qa", "y", "trash", "hm", "aco", "lgbt" diff --git a/src/services/command.ts b/src/services/command.ts new file mode 100644 index 0000000..b2309c7 --- /dev/null +++ b/src/services/command.ts @@ -0,0 +1,23 @@ +import type { Message } from "discord.js"; +import { readdir } from "fs/promises"; +import type { Command } from "../models"; + +const CMD_PREFIX = "."; + +export default abstract class CommandService { + private static registry: Record = {} + + static async init() { + const commands = await Promise.all( + (await readdir(`${import.meta.dir}/registry`)).map(async file => (await import(`./registry/${file}`)).default satisfies Command) + ); + for (const command of commands) + CommandService.registry[command.name] = command; + }; + + static parse(message: Message): Command | undefined { + if (!message.content.startsWith(CMD_PREFIX)) return; + const name = message.content.slice(CMD_PREFIX.length).split(/\s+/)[0]; + return this.registry[name ?? ""]; + } +} diff --git a/src/services/role.ts b/src/services/role.ts index 37dfe33..ae16937 100644 --- a/src/services/role.ts +++ b/src/services/role.ts @@ -1,6 +1,6 @@ -import type { Client, Guild, GuildMember, Collection, RoleCreateOptions } from "discord.js"; +import type { Guild, GuildMember, Collection, RoleCreateOptions } from "discord.js"; -export default class RoleService { +export default abstract class RoleService { static readonly ROLES: Record = { // Main "ROBOT": { @@ -59,20 +59,14 @@ export default class RoleService { static readonly STARTER_ROLES = [RoleService.ROLES["LURKER"], RoleService.ROLES["NEWFAG"]]; - private client: Client - - constructor(client: Client) { - this.client = client; - } - - validate(guilds: Collection): void { + static validate(guilds: Collection): void { guilds.forEach(guild => { const missing = Object.values(RoleService.ROLES).filter(role => !this.getRoleByName(guild, role.name)); if (missing.length) console.error(`${guild.name} is missing roles: ${missing.map(r => r.name).join(", ")}`); }); } - async createRoles(guild: Guild, roles: RoleCreateOptions[]) { + static async createRoles(guild: Guild, roles: RoleCreateOptions[]) { for (const role of roles) { try { await guild.roles.create(role); @@ -83,7 +77,7 @@ export default class RoleService { } } - async giveStarterRoles(guildMember: GuildMember) { + static async giveStarterRoles(guildMember: GuildMember) { for (const starterRole of RoleService.STARTER_ROLES) { const role = this.getRoleByName(guildMember.guild, starterRole?.name); if (!role) continue; @@ -97,7 +91,7 @@ export default class RoleService { } } - getRoleByName(guild: Guild, roleName?: string) { + static getRoleByName(guild: Guild, roleName?: string) { return guild.roles.cache.find(role => role.name === roleName); } }