import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, GatewayIntentBits, Guild, IntentsBitField, Snowflake, TextChannel } from "discord.js"; import { CommandType } from "../types/commandTypes"; import fs from 'fs' import { config } from "../configuration"; import { logger } from "../logger"; import { JellyfinHandler } from "../jellyfin/handler"; export class ExtendedClient extends Client { private eventFilePath = `${__dirname}/../events` private commandFilePath = `${__dirname}/../commands` private jellyfin: JellyfinHandler public commands: Collection = new Collection() private announcementChannels: Collection = new Collection //guildId to TextChannel public constructor(jf: JellyfinHandler) { const intents: IntentsBitField = new IntentsBitField() intents.add(IntentsBitField.Flags.GuildMembers, IntentsBitField.Flags.MessageContent, IntentsBitField.Flags.Guilds, IntentsBitField.Flags.DirectMessages, IntentsBitField.Flags.GuildScheduledEvents, IntentsBitField.Flags.GuildVoiceStates) const options: ClientOptions = { intents } super(options) this.jellyfin = jf } public async start() { if (process.env.NODE_ENV === 'test') return const promises: Promise[] = [] promises.push(this.registerSlashCommands()) promises.push(this.registerEventCallback()) Promise.all(promises).then(() => { this.login(config.bot.token) }) logger.info(`Connected with ${await this.jellyfin.ServerName()}`) } private async importFile(filepath: string): Promise { logger.debug(`Importing ${filepath}`) const imported = await import(filepath) logger.debug(`Imported ${JSON.stringify(imported)}`) return imported.default ?? imported } public async registerCommands(cmds: ApplicationCommandDataResolvable[], guildIds: Collection) { if (guildIds) { guildIds.forEach(guild => { this.guilds.cache.get(guild.id)?.commands.set(cmds) logger.info(`Registering commands to ${guild.name}|${guild.id}`) }) } else { this.application?.commands.set(cmds) logger.info(`Registering global commands`) } return } public async registerSlashCommands(): Promise { try { const slashCommands: ApplicationCommandDataResolvable[] = [] const commandFiles = fs.readdirSync(this.commandFilePath).filter(file => file.endsWith('.ts') || file.endsWith('.js')) for (const commandFile of commandFiles) { const filePath = `${this.commandFilePath}/${commandFile}` const command = await this.importFile(filePath) logger.debug(JSON.stringify(command)) if (!command.name) return this.commands.set(command.name, command) slashCommands.push(command) } this.on("ready", (client: Client) => { //logger.info(`Ready processing ${JSON.stringify(client)}`) logger.info(`SlashCommands: ${JSON.stringify(slashCommands)}`) const guilds = client.guilds.cache this.registerCommands(slashCommands, guilds) this.cacheUsers(guilds) this.cacheAnnouncementServer(guilds) }) } catch (error) { logger.info(`Error refreshing slash commands: ${error}`) } } private async cacheAnnouncementServer(guilds: Collection) { for (const guild of guilds.values()) { const channels: TextChannel[] = (await guild.channels.fetch()) ?.filter(channel => channel!.id === config.bot.announcement_channel_id) .map((value, _) => value) if (!channels || channels.length != 1) { logger.error(`Could not find announcement channel for guild ${guild.name} with guildId ${guild.id}. Found ${channels}`) continue } logger.info(`Fetched announcement channel: ${JSON.stringify(channels[0])}`) this.announcementChannels.set(guild.id, channels[0]) } } public getAnnouncementChannelForGuild(guildId: string): TextChannel { return this.announcementChannels.get(guildId)! //we set the channel by ourselves only if we find one, I think this is sage (mark my words) } public async cacheUsers(guilds: Collection) { guilds.forEach((guild: Guild, id: Snowflake) => { logger.info(`Fetching members for ${guild.name}|${id}`) guild.members.fetch() logger.info(`Fetched: ${guild.memberCount} members`) }) } public async registerEventCallback() { try { const eventFiles = fs.readdirSync(this.eventFilePath).filter(file => file.endsWith('.ts') || file.endsWith('.js')); for (const file of eventFiles) { const filePath = `${this.eventFilePath}/${file}` const event = await this.importFile(filePath) if (event.once) { logger.info(`Registering once ${file}`) this.once(event.name, (...args: any[]) => event.execute(...args)) } else { logger.info(`Registering on ${file}`) this.on(event.name, (...args: any[]) => event.execute(...args)) } } logger.info(`Registered event names ${this.eventNames()}`) } catch (error) { logger.error(error) } } }