format more files
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Compile the repository / compile (pull_request) Successful in 1m12s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Compile the repository / compile (pull_request) Successful in 1m12s
				
			This commit is contained in:
		@@ -13,169 +13,169 @@ import { checkForPollsToClose } from "../commands/closepoll";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class ExtendedClient extends Client {
 | 
			
		||||
  private eventFilePath = `${__dirname}/../events`
 | 
			
		||||
  private commandFilePath = `${__dirname}/../commands`
 | 
			
		||||
  private jellyfin: JellyfinHandler
 | 
			
		||||
  public commands: Collection<string, CommandType> = new Collection()
 | 
			
		||||
  private announcementChannels: Collection<string, TextChannel> = new Collection() //guildId to TextChannel
 | 
			
		||||
  private announcementRoleHandlerTask: Collection<string, ScheduledTask> = new Collection() //one task per guild
 | 
			
		||||
  private pollCloseBackgroundTasks: Collection<string, ScheduledTask> = new Collection()
 | 
			
		||||
  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<any>[] = []
 | 
			
		||||
    promises.push(this.registerSlashCommands())
 | 
			
		||||
    promises.push(this.registerEventCallback())
 | 
			
		||||
    Promise.all(promises).then(() => {
 | 
			
		||||
      this.login(config.bot.token)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  private async importFile(filepath: string): Promise<any> {
 | 
			
		||||
    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<Snowflake, Guild>) {
 | 
			
		||||
    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<void> {
 | 
			
		||||
    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", async (client: Client) => {
 | 
			
		||||
        //logger.info(`Ready processing ${JSON.stringify(client)}`)
 | 
			
		||||
        logger.info(`SlashCommands: ${JSON.stringify(slashCommands)}`)
 | 
			
		||||
        const guilds = client.guilds.cache
 | 
			
		||||
	private eventFilePath = `${__dirname}/../events`
 | 
			
		||||
	private commandFilePath = `${__dirname}/../commands`
 | 
			
		||||
	private jellyfin: JellyfinHandler
 | 
			
		||||
	public commands: Collection<string, CommandType> = new Collection()
 | 
			
		||||
	private announcementChannels: Collection<string, TextChannel> = new Collection() //guildId to TextChannel
 | 
			
		||||
	private announcementRoleHandlerTask: Collection<string, ScheduledTask> = new Collection() //one task per guild
 | 
			
		||||
	private pollCloseBackgroundTasks: Collection<string, ScheduledTask> = new Collection()
 | 
			
		||||
	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<any>[] = []
 | 
			
		||||
		promises.push(this.registerSlashCommands())
 | 
			
		||||
		promises.push(this.registerEventCallback())
 | 
			
		||||
		Promise.all(promises).then(() => {
 | 
			
		||||
			this.login(config.bot.token)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	private async importFile(filepath: string): Promise<any> {
 | 
			
		||||
		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<Snowflake, Guild>) {
 | 
			
		||||
		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<void> {
 | 
			
		||||
		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", async (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)
 | 
			
		||||
        await this.cacheAnnouncementServer(guilds)
 | 
			
		||||
        this.startAnnouncementRoleBackgroundTask(guilds)
 | 
			
		||||
        this.startPollCloseBackgroundTasks()
 | 
			
		||||
      })
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      logger.info(`Error refreshing slash commands: ${error}`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  private async cacheAnnouncementServer(guilds: Collection<Snowflake, Guild>) {
 | 
			
		||||
    for (const guild of guilds.values()) {
 | 
			
		||||
      const channels: TextChannel[] = <TextChannel[]>(await guild.channels.fetch())
 | 
			
		||||
        ?.filter(channel => channel?.id === config.bot.announcement_channel_id)
 | 
			
		||||
        .map((value) => value)
 | 
			
		||||
				this.registerCommands(slashCommands, guilds)
 | 
			
		||||
				this.cacheUsers(guilds)
 | 
			
		||||
				await this.cacheAnnouncementServer(guilds)
 | 
			
		||||
				this.startAnnouncementRoleBackgroundTask(guilds)
 | 
			
		||||
				this.startPollCloseBackgroundTasks()
 | 
			
		||||
			})
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			logger.info(`Error refreshing slash commands: ${error}`)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	private async cacheAnnouncementServer(guilds: Collection<Snowflake, Guild>) {
 | 
			
		||||
		for (const guild of guilds.values()) {
 | 
			
		||||
			const channels: TextChannel[] = <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): Maybe<TextChannel> {
 | 
			
		||||
    return this.announcementChannels.get(guildId)
 | 
			
		||||
  }
 | 
			
		||||
  public async cacheUsers(guilds: Collection<Snowflake, Guild>) {
 | 
			
		||||
    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)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
			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): Maybe<TextChannel> {
 | 
			
		||||
		return this.announcementChannels.get(guildId)
 | 
			
		||||
	}
 | 
			
		||||
	public async cacheUsers(guilds: Collection<Snowflake, Guild>) {
 | 
			
		||||
		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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  public async startAnnouncementRoleBackgroundTask(guilds: Collection<string, Guild>) {
 | 
			
		||||
    for (const guild of guilds.values()) {
 | 
			
		||||
      logger.info("Starting background task for announcement role", { guildId: guild.id })
 | 
			
		||||
      const textChannel: Maybe<TextChannel> = this.getAnnouncementChannelForGuild(guild.id)
 | 
			
		||||
      if (!textChannel) {
 | 
			
		||||
        logger.error("Could not find announcement channel. Aborting", { guildId: guild.id })
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      this.announcementRoleHandlerTask.set(guild.id, schedule("*/10 * * * * *", async () => {
 | 
			
		||||
        const requestId = uuid()
 | 
			
		||||
        const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
 | 
			
		||||
	public async startAnnouncementRoleBackgroundTask(guilds: Collection<string, Guild>) {
 | 
			
		||||
		for (const guild of guilds.values()) {
 | 
			
		||||
			logger.info("Starting background task for announcement role", { guildId: guild.id })
 | 
			
		||||
			const textChannel: Maybe<TextChannel> = this.getAnnouncementChannelForGuild(guild.id)
 | 
			
		||||
			if (!textChannel) {
 | 
			
		||||
				logger.error("Could not find announcement channel. Aborting", { guildId: guild.id })
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			this.announcementRoleHandlerTask.set(guild.id, schedule("*/10 * * * * *", async () => {
 | 
			
		||||
				const requestId = uuid()
 | 
			
		||||
				const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
 | 
			
		||||
 | 
			
		||||
        if (messages.size > 1) {
 | 
			
		||||
          logger.error("More than one pinned announcement Messages found. Unable to know which one people react to. Please fix!", { guildId: guild.id, requestId })
 | 
			
		||||
          return
 | 
			
		||||
        } else if (messages.size == 0) {
 | 
			
		||||
          logger.error("Could not find any pinned announcement messages. Unable to manage roles!", { guildId: guild.id, requestId })
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
				if (messages.size > 1) {
 | 
			
		||||
					logger.error("More than one pinned announcement Messages found. Unable to know which one people react to. Please fix!", { guildId: guild.id, requestId })
 | 
			
		||||
					return
 | 
			
		||||
				} else if (messages.size == 0) {
 | 
			
		||||
					logger.error("Could not find any pinned announcement messages. Unable to manage roles!", { guildId: guild.id, requestId })
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
        const message = await messages.at(0)?.fetch()
 | 
			
		||||
        if (!message) {
 | 
			
		||||
          logger.error(`No pinned message found`, { guildId: guild.id, requestId })
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        //logger.debug(`Message: ${JSON.stringify(message, null, 2)}`, { guildId: guild.id, requestId })
 | 
			
		||||
				const message = await messages.at(0)?.fetch()
 | 
			
		||||
				if (!message) {
 | 
			
		||||
					logger.error(`No pinned message found`, { guildId: guild.id, requestId })
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				//logger.debug(`Message: ${JSON.stringify(message, null, 2)}`, { guildId: guild.id, requestId })
 | 
			
		||||
 | 
			
		||||
        const reactions = message.reactions.resolve("🎫")
 | 
			
		||||
        //logger.debug(`reactions: ${JSON.stringify(reactions, null, 2)}`, { guildId: guild.id, requestId })
 | 
			
		||||
        if (reactions) {
 | 
			
		||||
          manageAnnouncementRoles(message.guild, reactions, requestId)
 | 
			
		||||
        } else {
 | 
			
		||||
          logger.error("Did not get reactions! Aborting!", { guildId: guild.id, requestId })
 | 
			
		||||
        }
 | 
			
		||||
      }))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
				const reactions = message.reactions.resolve("🎫")
 | 
			
		||||
				//logger.debug(`reactions: ${JSON.stringify(reactions, null, 2)}`, { guildId: guild.id, requestId })
 | 
			
		||||
				if (reactions) {
 | 
			
		||||
					manageAnnouncementRoles(message.guild, reactions, requestId)
 | 
			
		||||
				} else {
 | 
			
		||||
					logger.error("Did not get reactions! Aborting!", { guildId: guild.id, requestId })
 | 
			
		||||
				}
 | 
			
		||||
			}))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  public stopAnnouncementRoleBackgroundTask(guildId: string, requestId: string) {
 | 
			
		||||
    const task: Maybe<ScheduledTask> = this.announcementRoleHandlerTask.get(guildId)
 | 
			
		||||
    if (!task) {
 | 
			
		||||
      logger.error(`No task found for guildID ${guildId}.`, { guildId, requestId })
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    task.stop()
 | 
			
		||||
  }
 | 
			
		||||
	public stopAnnouncementRoleBackgroundTask(guildId: string, requestId: string) {
 | 
			
		||||
		const task: Maybe<ScheduledTask> = this.announcementRoleHandlerTask.get(guildId)
 | 
			
		||||
		if (!task) {
 | 
			
		||||
			logger.error(`No task found for guildID ${guildId}.`, { guildId, requestId })
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		task.stop()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
  private async startPollCloseBackgroundTasks() {
 | 
			
		||||
    for (const guild of this.guilds.cache) {
 | 
			
		||||
      this.pollCloseBackgroundTasks.set(guild[1].id, schedule("0 * * * * *", () => checkForPollsToClose(guild[1])))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
	private async startPollCloseBackgroundTasks() {
 | 
			
		||||
		for (const guild of this.guilds.cache) {
 | 
			
		||||
			this.pollCloseBackgroundTasks.set(guild[1].id, schedule("0 * * * * *", () => checkForPollsToClose(guild[1])))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { CommandType } from "../types/commandTypes";
 | 
			
		||||
 | 
			
		||||
export class Command {
 | 
			
		||||
  constructor(commandOptions: CommandType) {
 | 
			
		||||
    Object.assign(this, commandOptions)
 | 
			
		||||
  }
 | 
			
		||||
	constructor(commandOptions: CommandType) {
 | 
			
		||||
		Object.assign(this, commandOptions)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import { ClientEvents } from "discord.js";
 | 
			
		||||
 | 
			
		||||
export class Event<Key extends keyof ClientEvents>{
 | 
			
		||||
  constructor(
 | 
			
		||||
    public event: Key,
 | 
			
		||||
    public run: (...args: ClientEvents[Key]) => unknown
 | 
			
		||||
  ) { }
 | 
			
		||||
	constructor(
 | 
			
		||||
		public event: Key,
 | 
			
		||||
		public run: (...args: ClientEvents[Key]) => unknown
 | 
			
		||||
	) { }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user