feat/formatting #53
							
								
								
									
										4
									
								
								index.ts
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								index.ts
									
									
									
									
									
								
							@@ -5,8 +5,8 @@ import { JellyfinHandler } from "./server/jellyfin/handler"
 | 
			
		||||
import { attachedImages } from "./server/assets/attachments"
 | 
			
		||||
const requestId = 'startup'
 | 
			
		||||
 | 
			
		||||
export const jellyfinHandler = new JellyfinHandler({jellyfinToken: config.bot.workaround_token, jellyfinUrl: config.bot.jellyfin_url, movieCollectionId: config.bot.jf_collection_id, collectionUser: config.bot.jf_user})
 | 
			
		||||
export const yavinJellyfinHandler = new JellyfinHandler({jellyfinToken: config.bot.yavin_jellyfin_token, jellyfinUrl: config.bot.yavin_jellyfin_url, movieCollectionId: config.bot.yavin_collection_id, collectionUser: config.bot.yavin_jellyfin_collection_user})
 | 
			
		||||
export const jellyfinHandler = new JellyfinHandler({ jellyfinToken: config.bot.workaround_token, jellyfinUrl: config.bot.jellyfin_url, movieCollectionId: config.bot.jf_collection_id, collectionUser: config.bot.jf_user })
 | 
			
		||||
export const yavinJellyfinHandler = new JellyfinHandler({ jellyfinToken: config.bot.yavin_jellyfin_token, jellyfinUrl: config.bot.yavin_jellyfin_url, movieCollectionId: config.bot.yavin_collection_id, collectionUser: config.bot.yavin_jellyfin_collection_user })
 | 
			
		||||
 | 
			
		||||
export const client = new ExtendedClient(jellyfinHandler)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,4 +46,4 @@
 | 
			
		||||
    "rimraf": "^5.0.0",
 | 
			
		||||
    "ts-jest": "^29.1.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -8,114 +8,114 @@ import { Command } from '../structures/command'
 | 
			
		||||
import { RunOptions } from '../types/commandTypes'
 | 
			
		||||
 | 
			
		||||
export default new Command({
 | 
			
		||||
    name: 'announce',
 | 
			
		||||
    description: 'Neues announcement im announcement Channel an alle senden.',
 | 
			
		||||
    options: [{
 | 
			
		||||
        name: "typ",
 | 
			
		||||
        type: ApplicationCommandOptionType.String,
 | 
			
		||||
        description:"Was für ein announcement?",
 | 
			
		||||
        choices: [{name: "initial", value:"initial"},{name: "votepls", value:"votepls"},{name: "cancel", value:"cancel"}],
 | 
			
		||||
        required: true
 | 
			
		||||
    }],
 | 
			
		||||
    run: async (interaction: RunOptions) => {
 | 
			
		||||
        const command = interaction.interaction
 | 
			
		||||
        const requestId = uuid()
 | 
			
		||||
        if(!command.guildId) {
 | 
			
		||||
            logger.error("COMMAND DOES NOT HAVE A GUILD ID; CANCELLING!!!", {requestId})
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        const guildId = command.guildId
 | 
			
		||||
        const announcementType = command.options.data.find(option => option.name.includes("typ"))
 | 
			
		||||
        logger.info(`Got command for announcing ${announcementType?.value}!`, { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
        if(!announcementType) {
 | 
			
		||||
            logger.error("Did not get an announcement type!", { guildId, requestId })
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!isAdmin(command.member)) {
 | 
			
		||||
            logger.info(`Announcement was requested by ${command.member.displayName} but they are not an admin! Not sending announcement.`, { guildId, requestId })
 | 
			
		||||
            return
 | 
			
		||||
        } else {
 | 
			
		||||
            logger.info(`User ${command.member.displayName} seems to be admin`)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if((<string>announcementType.value).includes("initial")) {
 | 
			
		||||
            sendInitialAnnouncement(guildId, requestId)
 | 
			
		||||
            command.followUp("Ist rausgeschickt!")
 | 
			
		||||
        } else {
 | 
			
		||||
            command.followUp(`${announcementType.value} ist aktuell noch nicht implementiert`)
 | 
			
		||||
        }
 | 
			
		||||
  name: 'announce',
 | 
			
		||||
  description: 'Neues announcement im announcement Channel an alle senden.',
 | 
			
		||||
  options: [{
 | 
			
		||||
    name: "typ",
 | 
			
		||||
    type: ApplicationCommandOptionType.String,
 | 
			
		||||
    description: "Was für ein announcement?",
 | 
			
		||||
    choices: [{ name: "initial", value: "initial" }, { name: "votepls", value: "votepls" }, { name: "cancel", value: "cancel" }],
 | 
			
		||||
    required: true
 | 
			
		||||
  }],
 | 
			
		||||
  run: async (interaction: RunOptions) => {
 | 
			
		||||
    const command = interaction.interaction
 | 
			
		||||
    const requestId = uuid()
 | 
			
		||||
    if (!command.guildId) {
 | 
			
		||||
      logger.error("COMMAND DOES NOT HAVE A GUILD ID; CANCELLING!!!", { requestId })
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    const guildId = command.guildId
 | 
			
		||||
    const announcementType = command.options.data.find(option => option.name.includes("typ"))
 | 
			
		||||
    logger.info(`Got command for announcing ${announcementType?.value}!`, { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
    if (!announcementType) {
 | 
			
		||||
      logger.error("Did not get an announcement type!", { guildId, requestId })
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!isAdmin(command.member)) {
 | 
			
		||||
      logger.info(`Announcement was requested by ${command.member.displayName} but they are not an admin! Not sending announcement.`, { guildId, requestId })
 | 
			
		||||
      return
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.info(`User ${command.member.displayName} seems to be admin`)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ((<string>announcementType.value).includes("initial")) {
 | 
			
		||||
      sendInitialAnnouncement(guildId, requestId)
 | 
			
		||||
      command.followUp("Ist rausgeschickt!")
 | 
			
		||||
    } else {
 | 
			
		||||
      command.followUp(`${announcementType.value} ist aktuell noch nicht implementiert`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function isAdmin(member: GuildMember): boolean {
 | 
			
		||||
    return member.roles.cache.find((role) => role.id === config.bot.jf_admin_role) !== undefined
 | 
			
		||||
  return member.roles.cache.find((role) => role.id === config.bot.jf_admin_role) !== undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function sendInitialAnnouncement(guildId: string, requestId: string): Promise<void> {
 | 
			
		||||
    logger.info("Sending initial announcement")
 | 
			
		||||
    const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
 | 
			
		||||
    if(!announcementChannel) {
 | 
			
		||||
        logger.error("Could not find announcement channel. Aborting", { guildId, requestId })
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
  logger.info("Sending initial announcement")
 | 
			
		||||
  const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
 | 
			
		||||
  if (!announcementChannel) {
 | 
			
		||||
    logger.error("Could not find announcement channel. Aborting", { guildId, requestId })
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const currentPinnedAnnouncementMessages = (await announcementChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
 | 
			
		||||
    currentPinnedAnnouncementMessages.forEach(async (message) => await message.unpin())
 | 
			
		||||
    currentPinnedAnnouncementMessages.forEach(message => message.delete())
 | 
			
		||||
  const currentPinnedAnnouncementMessages = (await announcementChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
 | 
			
		||||
  currentPinnedAnnouncementMessages.forEach(async (message) => await message.unpin())
 | 
			
		||||
  currentPinnedAnnouncementMessages.forEach(message => message.delete())
 | 
			
		||||
 | 
			
		||||
    const body = `[initial] Hey! @everyone! Hier ist der Watchparty Bot vom Hartzarett. 
 | 
			
		||||
  const body = `[initial] Hey! @everyone! Hier ist der Watchparty Bot vom Hartzarett. 
 | 
			
		||||
    
 | 
			
		||||
Wir machen in Zukunft regelmäßig Watchparties in denen wir zusammen Filme gucken! Falls du mitmachen möchtest, reagiere einfach auf diesen Post mit 🎫, dann bekommst du automatisch eine Rolle zugewiesen und wirst benachrichtigt sobald es in der Zukunft weitere Watchparties und Filme zum abstimmen gibt.
 | 
			
		||||
 | 
			
		||||
Für eine Erklärung wie das alles funktioniert mach einfach /mitgucken für eine lange Erklärung am Stück oder /guides wenn du auswählen möchtest wozu du Infos bekommst.`
 | 
			
		||||
 | 
			
		||||
    const options: MessageCreateOptions = {
 | 
			
		||||
        allowedMentions: { parse: ['everyone'] },
 | 
			
		||||
        content: body
 | 
			
		||||
    }
 | 
			
		||||
    const message: Message<true> = await announcementChannel.send(options)
 | 
			
		||||
    await message.react("🎫")
 | 
			
		||||
    await message.pin()
 | 
			
		||||
  const options: MessageCreateOptions = {
 | 
			
		||||
    allowedMentions: { parse: ['everyone'] },
 | 
			
		||||
    content: body
 | 
			
		||||
  }
 | 
			
		||||
  const message: Message<true> = await announcementChannel.send(options)
 | 
			
		||||
  await message.react("🎫")
 | 
			
		||||
  await message.pin()
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function manageAnnouncementRoles(guild: Guild, reaction: MessageReaction, requestId: string) {
 | 
			
		||||
    const guildId = guild.id
 | 
			
		||||
    logger.info("Managing roles", { guildId, requestId })
 | 
			
		||||
  const guildId = guild.id
 | 
			
		||||
  logger.info("Managing roles", { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
    const announcementRole: Role | undefined = (await guild.roles.fetch()).find(role => role.id === config.bot.announcement_role)
 | 
			
		||||
    if (!announcementRole) {
 | 
			
		||||
        logger.error(`Could not find announcement role! Aborting! Was looking for role with id: ${config.bot.announcement_role}`, { guildId, requestId })
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
  const announcementRole: Role | undefined = (await guild.roles.fetch()).find(role => role.id === config.bot.announcement_role)
 | 
			
		||||
  if (!announcementRole) {
 | 
			
		||||
    logger.error(`Could not find announcement role! Aborting! Was looking for role with id: ${config.bot.announcement_role}`, { guildId, requestId })
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const usersWhoWantRole: User[] = (await reaction.users.fetch()).filter(user => !user.bot).map(user => user)
 | 
			
		||||
  const usersWhoWantRole: User[] = (await reaction.users.fetch()).filter(user => !user.bot).map(user => user)
 | 
			
		||||
 | 
			
		||||
    const allUsers = (await guild.members.fetch())
 | 
			
		||||
  const allUsers = (await guild.members.fetch())
 | 
			
		||||
 | 
			
		||||
    const usersWhoHaveRole: GuildMember[] = allUsers
 | 
			
		||||
        .filter(member=> member.roles.cache
 | 
			
		||||
            .find(role => role.id === config.bot.announcement_role) !== undefined)
 | 
			
		||||
        .map(member => member)
 | 
			
		||||
  const usersWhoHaveRole: GuildMember[] = allUsers
 | 
			
		||||
    .filter(member => member.roles.cache
 | 
			
		||||
      .find(role => role.id === config.bot.announcement_role) !== undefined)
 | 
			
		||||
    .map(member => member)
 | 
			
		||||
 | 
			
		||||
    const usersWhoNeedRoleRevoked: GuildMember[] = usersWhoHaveRole
 | 
			
		||||
        .filter(userWhoHas => !usersWhoWantRole.map(wanter => wanter.id).includes(userWhoHas.id))
 | 
			
		||||
  const usersWhoNeedRoleRevoked: GuildMember[] = usersWhoHaveRole
 | 
			
		||||
    .filter(userWhoHas => !usersWhoWantRole.map(wanter => wanter.id).includes(userWhoHas.id))
 | 
			
		||||
 | 
			
		||||
    const usersWhoDontHaveRole: GuildMember[] = allUsers
 | 
			
		||||
        .filter(member => member.roles.cache
 | 
			
		||||
            .find(role=> role.id === config.bot.announcement_role) === undefined)
 | 
			
		||||
        .map(member => member)
 | 
			
		||||
  const usersWhoDontHaveRole: GuildMember[] = allUsers
 | 
			
		||||
    .filter(member => member.roles.cache
 | 
			
		||||
      .find(role => role.id === config.bot.announcement_role) === undefined)
 | 
			
		||||
    .map(member => member)
 | 
			
		||||
 | 
			
		||||
    const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole
 | 
			
		||||
        .filter(userWhoNeeds => usersWhoWantRole.map(wanter => wanter.id).includes(userWhoNeeds.id))
 | 
			
		||||
  const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole
 | 
			
		||||
    .filter(userWhoNeeds => usersWhoWantRole.map(wanter => wanter.id).includes(userWhoNeeds.id))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    logger.debug(`Theses users will get the role removed: ${JSON.stringify(usersWhoNeedRoleRevoked)}`, {guildId, requestId})
 | 
			
		||||
    logger.debug(`Theses users will get the role added: ${JSON.stringify(usersWhoNeedRole)}`, {guildId, requestId})
 | 
			
		||||
  logger.debug(`Theses users will get the role removed: ${JSON.stringify(usersWhoNeedRoleRevoked)}`, { guildId, requestId })
 | 
			
		||||
  logger.debug(`Theses users will get the role added: ${JSON.stringify(usersWhoNeedRole)}`, { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
    usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole))
 | 
			
		||||
    usersWhoNeedRole.forEach(user => user.roles.add(announcementRole))
 | 
			
		||||
  usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole))
 | 
			
		||||
  usersWhoNeedRole.forEach(user => user.roles.add(announcementRole))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,175 +10,175 @@ import { Command } from '../structures/command'
 | 
			
		||||
import { RunOptions } from '../types/commandTypes'
 | 
			
		||||
 | 
			
		||||
export default new Command({
 | 
			
		||||
    name: 'closepoll',
 | 
			
		||||
    description: 'Aktuelle Umfrage für nächste Watchparty beenden und Gewinner in Event eintragen.',
 | 
			
		||||
    options: [],
 | 
			
		||||
    run: async (interaction: RunOptions) => {
 | 
			
		||||
        const command = interaction.interaction
 | 
			
		||||
        const requestId = uuid()
 | 
			
		||||
        if (!command.guild) {
 | 
			
		||||
            logger.error("No guild found in interaction. Cancelling closing request", { requestId })
 | 
			
		||||
            command.followUp("Es gab leider ein Problem. Ich konnte deine Anfrage nicht bearbeiten :(")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        const guildId = command.guildId
 | 
			
		||||
        logger.info("Got command for closing poll!", { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
        command.followUp("Alles klar, beende die Umfrage :)")
 | 
			
		||||
        closePoll(command.guild, requestId)
 | 
			
		||||
  name: 'closepoll',
 | 
			
		||||
  description: 'Aktuelle Umfrage für nächste Watchparty beenden und Gewinner in Event eintragen.',
 | 
			
		||||
  options: [],
 | 
			
		||||
  run: async (interaction: RunOptions) => {
 | 
			
		||||
    const command = interaction.interaction
 | 
			
		||||
    const requestId = uuid()
 | 
			
		||||
    if (!command.guild) {
 | 
			
		||||
      logger.error("No guild found in interaction. Cancelling closing request", { requestId })
 | 
			
		||||
      command.followUp("Es gab leider ein Problem. Ich konnte deine Anfrage nicht bearbeiten :(")
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    const guildId = command.guildId
 | 
			
		||||
    logger.info("Got command for closing poll!", { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
    command.followUp("Alles klar, beende die Umfrage :)")
 | 
			
		||||
    closePoll(command.guild, requestId)
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export async function closePoll(guild: Guild, requestId: string) {
 | 
			
		||||
    const guildId = guild.id
 | 
			
		||||
    logger.info("stopping poll", { guildId, requestId })
 | 
			
		||||
  const guildId = guild.id
 | 
			
		||||
  logger.info("stopping poll", { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
    const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
 | 
			
		||||
    if (!announcementChannel) {
 | 
			
		||||
        logger.error("Could not find the textchannel. Unable to close poll.", { guildId, requestId })
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
  const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
 | 
			
		||||
  if (!announcementChannel) {
 | 
			
		||||
    logger.error("Could not find the textchannel. Unable to close poll.", { guildId, requestId })
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const messages: Message<true>[] = (await announcementChannel.messages.fetch()) //todo: fetch only pinned messages
 | 
			
		||||
        .map((value) => value)
 | 
			
		||||
        .filter(message => !message.cleanContent.includes("[Abstimmung beendet]") && message.cleanContent.includes("[Abstimmung]"))
 | 
			
		||||
        .sort((a, b) => b.createdTimestamp - a.createdTimestamp)
 | 
			
		||||
  const messages: Message<true>[] = (await announcementChannel.messages.fetch()) //todo: fetch only pinned messages
 | 
			
		||||
    .map((value) => value)
 | 
			
		||||
    .filter(message => !message.cleanContent.includes("[Abstimmung beendet]") && message.cleanContent.includes("[Abstimmung]"))
 | 
			
		||||
    .sort((a, b) => b.createdTimestamp - a.createdTimestamp)
 | 
			
		||||
 | 
			
		||||
    if (!messages || messages.length <= 0) {
 | 
			
		||||
        logger.info("Could not find any vote messages. Cancelling pollClose", { guildId, requestId })
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
  if (!messages || messages.length <= 0) {
 | 
			
		||||
    logger.info("Could not find any vote messages. Cancelling pollClose", { guildId, requestId })
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const lastMessage: Message<true> = messages[0]
 | 
			
		||||
  const lastMessage: Message<true> = messages[0]
 | 
			
		||||
 | 
			
		||||
    logger.debug(`Found messages: ${JSON.stringify(messages, null, 2)}`, { guildId, requestId })
 | 
			
		||||
  logger.debug(`Found messages: ${JSON.stringify(messages, null, 2)}`, { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
    logger.debug(`Last message: ${JSON.stringify(lastMessage, null, 2)}`, { guildId, requestId })
 | 
			
		||||
  logger.debug(`Last message: ${JSON.stringify(lastMessage, null, 2)}`, { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    const votes = await (await getVotesByEmote(lastMessage, guildId, requestId))
 | 
			
		||||
        .sort((a, b) => b.count - a.count)
 | 
			
		||||
  const votes = await (await getVotesByEmote(lastMessage, guildId, requestId))
 | 
			
		||||
    .sort((a, b) => b.count - a.count)
 | 
			
		||||
 | 
			
		||||
    logger.debug(`votes: ${JSON.stringify(votes, null, 2)}`, { guildId, requestId })
 | 
			
		||||
  logger.debug(`votes: ${JSON.stringify(votes, null, 2)}`, { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
    logger.info("Deleting vote message")
 | 
			
		||||
    await lastMessage.delete()
 | 
			
		||||
    const event = await getEvent(guild, guild.id, requestId)
 | 
			
		||||
    if (event) {
 | 
			
		||||
        updateEvent(event, votes, guild, guildId, requestId)
 | 
			
		||||
        sendVoteClosedMessage(event, votes[0].movie, guildId, requestId)
 | 
			
		||||
    }
 | 
			
		||||
  logger.info("Deleting vote message")
 | 
			
		||||
  await lastMessage.delete()
 | 
			
		||||
  const event = await getEvent(guild, guild.id, requestId)
 | 
			
		||||
  if (event) {
 | 
			
		||||
    updateEvent(event, votes, guild, guildId, requestId)
 | 
			
		||||
    sendVoteClosedMessage(event, votes[0].movie, guildId, requestId)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    //lastMessage.unpin() //todo: uncomment when bot has permission to pin/unpin
 | 
			
		||||
  //lastMessage.unpin() //todo: uncomment when bot has permission to pin/unpin
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function sendVoteClosedMessage(event: GuildScheduledEvent, movie: string, guildId: string, requestId: string) {
 | 
			
		||||
    const date = event.scheduledStartAt ? format(event.scheduledStartAt, "dd.MM") : "Fehler, event hatte kein Datum"
 | 
			
		||||
    const time = event.scheduledStartAt ? format(event.scheduledStartAt, "HH:mm") : "Fehler, event hatte kein Datum"
 | 
			
		||||
    const body = `[Abstimmung beendet] für https://discord.com/events/${event.guildId}/${event.id}\n<@&${config.bot.announcement_role}> Wir gucken  ${movie} am ${date} um ${time}`
 | 
			
		||||
    const options: MessageCreateOptions = {
 | 
			
		||||
        content: body,
 | 
			
		||||
        allowedMentions: { parse: ["roles"] }
 | 
			
		||||
    }
 | 
			
		||||
    const announcementChannel = client.getAnnouncementChannelForGuild(guildId)
 | 
			
		||||
    logger.info("Sending vote closed message.", { guildId, requestId })
 | 
			
		||||
    if (!announcementChannel) {
 | 
			
		||||
        logger.error("Could not find announcement channel. Please fix!", { guildId, requestId })
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
    announcementChannel.send(options)
 | 
			
		||||
  const date = event.scheduledStartAt ? format(event.scheduledStartAt, "dd.MM") : "Fehler, event hatte kein Datum"
 | 
			
		||||
  const time = event.scheduledStartAt ? format(event.scheduledStartAt, "HH:mm") : "Fehler, event hatte kein Datum"
 | 
			
		||||
  const body = `[Abstimmung beendet] für https://discord.com/events/${event.guildId}/${event.id}\n<@&${config.bot.announcement_role}> Wir gucken  ${movie} am ${date} um ${time}`
 | 
			
		||||
  const options: MessageCreateOptions = {
 | 
			
		||||
    content: body,
 | 
			
		||||
    allowedMentions: { parse: ["roles"] }
 | 
			
		||||
  }
 | 
			
		||||
  const announcementChannel = client.getAnnouncementChannelForGuild(guildId)
 | 
			
		||||
  logger.info("Sending vote closed message.", { guildId, requestId })
 | 
			
		||||
  if (!announcementChannel) {
 | 
			
		||||
    logger.error("Could not find announcement channel. Please fix!", { guildId, requestId })
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  announcementChannel.send(options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function updateEvent(voteEvent: GuildScheduledEvent, votes: Vote[], guild: Guild, guildId: string, requestId: string) {
 | 
			
		||||
    logger.info(`Updating event with movie ${votes[0].movie}.`, { guildId, requestId })
 | 
			
		||||
    const options: GuildScheduledEventEditOptions<GuildScheduledEventStatus.Scheduled, GuildScheduledEventSetStatusArg<GuildScheduledEventStatus.Scheduled>> = {
 | 
			
		||||
        name: votes[0].movie,
 | 
			
		||||
        description: `!wp\nNummer 2: ${votes[1].movie} mit ${votes[1].count - 1} Stimmen\nNummer 3: ${votes[2].movie} mit ${votes[2].count - 1} Stimmen`
 | 
			
		||||
    }
 | 
			
		||||
    logger.debug(`Updating event: ${JSON.stringify(voteEvent, null, 2)}`, { guildId, requestId })
 | 
			
		||||
    logger.info("Updating event.", { guildId, requestId })
 | 
			
		||||
    voteEvent.edit(options)
 | 
			
		||||
  logger.info(`Updating event with movie ${votes[0].movie}.`, { guildId, requestId })
 | 
			
		||||
  const options: GuildScheduledEventEditOptions<GuildScheduledEventStatus.Scheduled, GuildScheduledEventSetStatusArg<GuildScheduledEventStatus.Scheduled>> = {
 | 
			
		||||
    name: votes[0].movie,
 | 
			
		||||
    description: `!wp\nNummer 2: ${votes[1].movie} mit ${votes[1].count - 1} Stimmen\nNummer 3: ${votes[2].movie} mit ${votes[2].count - 1} Stimmen`
 | 
			
		||||
  }
 | 
			
		||||
  logger.debug(`Updating event: ${JSON.stringify(voteEvent, null, 2)}`, { guildId, requestId })
 | 
			
		||||
  logger.info("Updating event.", { guildId, requestId })
 | 
			
		||||
  voteEvent.edit(options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getEvent(guild: Guild, guildId: string, requestId: string): Promise<GuildScheduledEvent | null> {
 | 
			
		||||
    const voteEvents = (await guild.scheduledEvents.fetch())
 | 
			
		||||
        .map((value) => value)
 | 
			
		||||
        .filter(event => event.name.toLowerCase().includes("voting offen"))
 | 
			
		||||
    logger.debug(`Found events: ${JSON.stringify(voteEvents, null, 2)}`, { guildId, requestId })
 | 
			
		||||
  const voteEvents = (await guild.scheduledEvents.fetch())
 | 
			
		||||
    .map((value) => value)
 | 
			
		||||
    .filter(event => event.name.toLowerCase().includes("voting offen"))
 | 
			
		||||
  logger.debug(`Found events: ${JSON.stringify(voteEvents, null, 2)}`, { guildId, requestId })
 | 
			
		||||
 | 
			
		||||
    if (!voteEvents || voteEvents.length <= 0) {
 | 
			
		||||
        logger.error("Could not find vote event. Cancelling update!", { guildId, requestId })
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
    return voteEvents[0]
 | 
			
		||||
  if (!voteEvents || voteEvents.length <= 0) {
 | 
			
		||||
    logger.error("Could not find vote event. Cancelling update!", { guildId, requestId })
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
  return voteEvents[0]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Vote = {
 | 
			
		||||
    emote: string, //todo habs nicht hinbekommen hier Emotes zu nutzen
 | 
			
		||||
    count: number,
 | 
			
		||||
    movie: string
 | 
			
		||||
  emote: string, //todo habs nicht hinbekommen hier Emotes zu nutzen
 | 
			
		||||
  count: number,
 | 
			
		||||
  movie: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getVotesByEmote(message: Message, guildId: string, requestId: string): Promise<Vote[]> {
 | 
			
		||||
    const votes: Vote[] = []
 | 
			
		||||
    logger.debug(`Number of items in emotes: ${Object.values(Emotes).length}`, { guildId, requestId })
 | 
			
		||||
    for (let i = 0; i < Object.keys(Emotes).length / 2; i++) {
 | 
			
		||||
        const emote = Emotes[i]
 | 
			
		||||
        logger.debug(`Getting reaction for emote ${emote}`, { guildId, requestId })
 | 
			
		||||
        const reaction = await message.reactions.resolve(emote)
 | 
			
		||||
        logger.debug(`Reaction for emote ${emote}: ${JSON.stringify(reaction, null, 2)}`, { guildId, requestId })
 | 
			
		||||
        if (reaction) {
 | 
			
		||||
            const vote: Vote = { emote: emote, count: reaction.count, movie: extractMovieFromMessageByEmote(message, emote) }
 | 
			
		||||
            votes.push(vote)
 | 
			
		||||
        }
 | 
			
		||||
  const votes: Vote[] = []
 | 
			
		||||
  logger.debug(`Number of items in emotes: ${Object.values(Emotes).length}`, { guildId, requestId })
 | 
			
		||||
  for (let i = 0; i < Object.keys(Emotes).length / 2; i++) {
 | 
			
		||||
    const emote = Emotes[i]
 | 
			
		||||
    logger.debug(`Getting reaction for emote ${emote}`, { guildId, requestId })
 | 
			
		||||
    const reaction = await message.reactions.resolve(emote)
 | 
			
		||||
    logger.debug(`Reaction for emote ${emote}: ${JSON.stringify(reaction, null, 2)}`, { guildId, requestId })
 | 
			
		||||
    if (reaction) {
 | 
			
		||||
      const vote: Vote = { emote: emote, count: reaction.count, movie: extractMovieFromMessageByEmote(message, emote) }
 | 
			
		||||
      votes.push(vote)
 | 
			
		||||
    }
 | 
			
		||||
    return votes
 | 
			
		||||
  }
 | 
			
		||||
  return votes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function extractMovieFromMessageByEmote(message: Message, emote: string): string {
 | 
			
		||||
    const lines = message.cleanContent.split("\n")
 | 
			
		||||
    const emoteLines = lines.filter(line => line.includes(emote))
 | 
			
		||||
  const lines = message.cleanContent.split("\n")
 | 
			
		||||
  const emoteLines = lines.filter(line => line.includes(emote))
 | 
			
		||||
 | 
			
		||||
    if (!emoteLines) {
 | 
			
		||||
        return ""
 | 
			
		||||
    }
 | 
			
		||||
    const movie = emoteLines[0].substring(emoteLines[0].indexOf(emote) + emote.length + 2) // plus colon and space
 | 
			
		||||
  if (!emoteLines) {
 | 
			
		||||
    return ""
 | 
			
		||||
  }
 | 
			
		||||
  const movie = emoteLines[0].substring(emoteLines[0].indexOf(emote) + emote.length + 2) // plus colon and space
 | 
			
		||||
 | 
			
		||||
    return movie
 | 
			
		||||
  return movie
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function checkForPollsToClose(guild: Guild): Promise<void> {
 | 
			
		||||
    const requestId = uuid()
 | 
			
		||||
    logger.info(`Automatic check for poll closing.`, { guildId: guild.id, requestId })
 | 
			
		||||
    const events = (await guild.scheduledEvents.fetch()).filter(event => event.name.toLocaleLowerCase().includes("voting offen")).map(event => event)
 | 
			
		||||
    if (events.length > 1) {
 | 
			
		||||
        logger.error("Handling more than one Event is not implemented yet. Found more than one poll to close")
 | 
			
		||||
        return
 | 
			
		||||
    } else if (events.length == 0) {
 | 
			
		||||
        logger.info("Could not find any events. Cancelling", { guildId: guild.id, requestId })
 | 
			
		||||
    }
 | 
			
		||||
  const requestId = uuid()
 | 
			
		||||
  logger.info(`Automatic check for poll closing.`, { guildId: guild.id, requestId })
 | 
			
		||||
  const events = (await guild.scheduledEvents.fetch()).filter(event => event.name.toLocaleLowerCase().includes("voting offen")).map(event => event)
 | 
			
		||||
  if (events.length > 1) {
 | 
			
		||||
    logger.error("Handling more than one Event is not implemented yet. Found more than one poll to close")
 | 
			
		||||
    return
 | 
			
		||||
  } else if (events.length == 0) {
 | 
			
		||||
    logger.info("Could not find any events. Cancelling", { guildId: guild.id, requestId })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const updatedEvent = events[0]             //add two hours because of different timezones in discord api and Date.now()
 | 
			
		||||
    if (!updatedEvent.scheduledStartTimestamp) {
 | 
			
		||||
        logger.error("Event does not have a scheduled start time. Cancelling", { guildId: guild.id, requestId })
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
  const updatedEvent = events[0]             //add two hours because of different timezones in discord api and Date.now()
 | 
			
		||||
  if (!updatedEvent.scheduledStartTimestamp) {
 | 
			
		||||
    logger.error("Event does not have a scheduled start time. Cancelling", { guildId: guild.id, requestId })
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const createDate: Date = toDate(updatedEvent.createdTimestamp)
 | 
			
		||||
    const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp)
 | 
			
		||||
    const difference: number = differenceInDays(createDate, eventDate)
 | 
			
		||||
  const createDate: Date = toDate(updatedEvent.createdTimestamp)
 | 
			
		||||
  const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp)
 | 
			
		||||
  const difference: number = differenceInDays(createDate, eventDate)
 | 
			
		||||
 | 
			
		||||
    if (difference <= 2) {
 | 
			
		||||
        logger.info("Less than two days between event create and event start. Not closing poll.", { guildId: guild.id, requestId })
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
  if (difference <= 2) {
 | 
			
		||||
    logger.info("Less than two days between event create and event start. Not closing poll.", { guildId: guild.id, requestId })
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const closePollDate: Date = addDays(eventDate, -2)
 | 
			
		||||
  const closePollDate: Date = addDays(eventDate, -2)
 | 
			
		||||
 | 
			
		||||
    if (isAfter(Date.now(), closePollDate)) {
 | 
			
		||||
        logger.info("Less than two days until event. Closing poll", { guildId: guild.id, requestId })
 | 
			
		||||
        closePoll(guild, requestId)
 | 
			
		||||
    } else {
 | 
			
		||||
        logger.info(`ScheduledStart: ${closePollDate}. Now: ${toDate(Date.now())}`, { guildId: guild.id, requestId })
 | 
			
		||||
    }
 | 
			
		||||
  if (isAfter(Date.now(), closePollDate)) {
 | 
			
		||||
    logger.info("Less than two days until event. Closing poll", { guildId: guild.id, requestId })
 | 
			
		||||
    closePoll(guild, requestId)
 | 
			
		||||
  } else {
 | 
			
		||||
    logger.info(`ScheduledStart: ${closePollDate}. Now: ${toDate(Date.now())}`, { guildId: guild.id, requestId })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,18 +2,18 @@ import { ApplicationCommandOptionType } from 'discord.js'
 | 
			
		||||
import { Command } from '../structures/command'
 | 
			
		||||
import { RunOptions } from '../types/commandTypes'
 | 
			
		||||
export default new Command({
 | 
			
		||||
	name: 'echo',
 | 
			
		||||
	description: 'Echoes a text',
 | 
			
		||||
	options: [
 | 
			
		||||
		{
 | 
			
		||||
			name: 'echo',
 | 
			
		||||
			description: 'The text to echo',
 | 
			
		||||
			type: ApplicationCommandOptionType.String,
 | 
			
		||||
			required: true
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
	run: async (interaction: RunOptions) => {
 | 
			
		||||
		console.log('echo called')
 | 
			
		||||
		interaction.interaction.reply(interaction.toString())
 | 
			
		||||
	}
 | 
			
		||||
  name: 'echo',
 | 
			
		||||
  description: 'Echoes a text',
 | 
			
		||||
  options: [
 | 
			
		||||
    {
 | 
			
		||||
      name: 'echo',
 | 
			
		||||
      description: 'The text to echo',
 | 
			
		||||
      type: ApplicationCommandOptionType.String,
 | 
			
		||||
      required: true
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  run: async (interaction: RunOptions) => {
 | 
			
		||||
    console.log('echo called')
 | 
			
		||||
    interaction.interaction.reply(interaction.toString())
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,13 @@ import { Command } from '../structures/command'
 | 
			
		||||
import { RunOptions } from '../types/commandTypes'
 | 
			
		||||
 | 
			
		||||
export default new Command({
 | 
			
		||||
	name: 'passwort_reset',
 | 
			
		||||
	description: 'Ich vergebe dir ein neues Passwort und schicke es dir per DM zu. Kostet auch nix! Versprochen! 😉',
 | 
			
		||||
	options: [],
 | 
			
		||||
	run: async (interaction: RunOptions) => {
 | 
			
		||||
		console.log('PasswortReset called')
 | 
			
		||||
		interaction.interaction.followUp('Yo, ich schick dir eins!')
 | 
			
		||||
		console.log(JSON.stringify(interaction.interaction.member, null, 2))
 | 
			
		||||
		jellyfinHandler.resetUserPasswort(interaction.interaction.member, uuid())
 | 
			
		||||
	}
 | 
			
		||||
  name: 'passwort_reset',
 | 
			
		||||
  description: 'Ich vergebe dir ein neues Passwort und schicke es dir per DM zu. Kostet auch nix! Versprochen! 😉',
 | 
			
		||||
  options: [],
 | 
			
		||||
  run: async (interaction: RunOptions) => {
 | 
			
		||||
    console.log('PasswortReset called')
 | 
			
		||||
    interaction.interaction.followUp('Yo, ich schick dir eins!')
 | 
			
		||||
    console.log(JSON.stringify(interaction.interaction.member, null, 2))
 | 
			
		||||
    jellyfinHandler.resetUserPasswort(interaction.interaction.member, uuid())
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -10,39 +10,39 @@ import { logger } from "../logger";
 | 
			
		||||
export const name = 'guildScheduledEventCreate'
 | 
			
		||||
 | 
			
		||||
export async function execute(event: GuildScheduledEvent) {
 | 
			
		||||
    const guildId = event.guildId
 | 
			
		||||
    const requestId = uuid()
 | 
			
		||||
    try {
 | 
			
		||||
        if (!event.description) {
 | 
			
		||||
            logger.debug("Got GuildScheduledEventCreate event. But has no description. Aborting.")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (event.description.includes("!wp")) {
 | 
			
		||||
            logger.info("Got manual create event of watchparty event!", { guildId, requestId })
 | 
			
		||||
            if(event.description.includes("!private")) {
 | 
			
		||||
                logger.info("Event description contains \"!private\". Won't announce.", { guildId, requestId })
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const channel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
 | 
			
		||||
 | 
			
		||||
            if (!channel) {
 | 
			
		||||
                logger.error("Could not obtain announcement channel. Aborting announcement.", { guildId, requestId })
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const message = `[Watchparty] https://discord.com/events/${event.guildId}/${event.id} \nHey <@&${config.bot.announcement_role}>, wir gucken ${event.name} ${createDateStringFromEvent(event, guildId, requestId)}`
 | 
			
		||||
 | 
			
		||||
            channel.send(message)
 | 
			
		||||
        } else {
 | 
			
		||||
            logger.debug("Got GuildScheduledEventCreate event but no !wp in description. Not creating manual wp announcement.", { guildId, requestId })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        // sendFailureDM(error)
 | 
			
		||||
        logger.error(<string>error, { guildId, requestId })
 | 
			
		||||
  const guildId = event.guildId
 | 
			
		||||
  const requestId = uuid()
 | 
			
		||||
  try {
 | 
			
		||||
    if (!event.description) {
 | 
			
		||||
      logger.debug("Got GuildScheduledEventCreate event. But has no description. Aborting.")
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (event.description.includes("!wp")) {
 | 
			
		||||
      logger.info("Got manual create event of watchparty event!", { guildId, requestId })
 | 
			
		||||
      if (event.description.includes("!private")) {
 | 
			
		||||
        logger.info("Event description contains \"!private\". Won't announce.", { guildId, requestId })
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const channel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
 | 
			
		||||
 | 
			
		||||
      if (!channel) {
 | 
			
		||||
        logger.error("Could not obtain announcement channel. Aborting announcement.", { guildId, requestId })
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const message = `[Watchparty] https://discord.com/events/${event.guildId}/${event.id} \nHey <@&${config.bot.announcement_role}>, wir gucken ${event.name} ${createDateStringFromEvent(event, guildId, requestId)}`
 | 
			
		||||
 | 
			
		||||
      channel.send(message)
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.debug("Got GuildScheduledEventCreate event but no !wp in description. Not creating manual wp announcement.", { guildId, requestId })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    // sendFailureDM(error)
 | 
			
		||||
    logger.error(<string>error, { guildId, requestId })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -16,48 +16,48 @@ export const NONE_OF_THAT = "❌"
 | 
			
		||||
export let task: ScheduledTask | undefined
 | 
			
		||||
 | 
			
		||||
export async function execute(event: GuildScheduledEvent) {
 | 
			
		||||
    const requestId = uuid()
 | 
			
		||||
    
 | 
			
		||||
    if (event.name.toLowerCase().includes("!nextwp")) {
 | 
			
		||||
        logger.info("Event was a placeholder event to start a new watchparty and voting. Creating vote!", { guildId: event.guildId, requestId })
 | 
			
		||||
        logger.debug("Renaming event", { guildId: event.guildId, requestId })
 | 
			
		||||
        event.edit({ name: "Watchparty - Voting offen" })
 | 
			
		||||
        const movies = await yavinJellyfinHandler.getRandomMovieNames(5, event.guildId, requestId)
 | 
			
		||||
  const requestId = uuid()
 | 
			
		||||
 | 
			
		||||
        logger.info(`Got ${movies.length} random movies. Creating voting`, { guildId: event.guildId, requestId })
 | 
			
		||||
        logger.debug(`Movies: ${JSON.stringify(movies)}`, { guildId: event.guildId, requestId })
 | 
			
		||||
  if (event.name.toLowerCase().includes("!nextwp")) {
 | 
			
		||||
    logger.info("Event was a placeholder event to start a new watchparty and voting. Creating vote!", { guildId: event.guildId, requestId })
 | 
			
		||||
    logger.debug("Renaming event", { guildId: event.guildId, requestId })
 | 
			
		||||
    event.edit({ name: "Watchparty - Voting offen" })
 | 
			
		||||
    const movies = await yavinJellyfinHandler.getRandomMovieNames(5, event.guildId, requestId)
 | 
			
		||||
 | 
			
		||||
        const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(event.guildId)
 | 
			
		||||
        if(!announcementChannel) {
 | 
			
		||||
            logger.error("Could not find announcement channel. Aborting", { guildId: event.guildId, requestId })
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId })
 | 
			
		||||
    logger.info(`Got ${movies.length} random movies. Creating voting`, { guildId: event.guildId, requestId })
 | 
			
		||||
    logger.debug(`Movies: ${JSON.stringify(movies)}`, { guildId: event.guildId, requestId })
 | 
			
		||||
 | 
			
		||||
        if(!event.scheduledStartAt) {
 | 
			
		||||
            logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", {guildId: event.guildId, requestId})
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        let message = `[Abstimmung] für https://discord.com/events/${event.guildId}/${event.id}\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty ${createDateStringFromEvent(event, event.guildId, requestId)}! Stimme hierunter für den nächsten Film ab!\n`
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < movies.length; i++) {
 | 
			
		||||
            message = message.concat(Emotes[i]).concat(": ").concat(movies[i]).concat("\n")
 | 
			
		||||
        }
 | 
			
		||||
        message = message.concat(NONE_OF_THAT).concat(": Wenn dir nichts davon gefällt.")
 | 
			
		||||
 | 
			
		||||
        const options: MessageCreateOptions = {
 | 
			
		||||
            allowedMentions: { parse: ["roles"]},
 | 
			
		||||
            content: message,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const sentMessage: Message<true> = await (await announcementChannel.fetch()).send(options)
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < movies.length; i++) {
 | 
			
		||||
            sentMessage.react(Emotes[i])
 | 
			
		||||
        }
 | 
			
		||||
        sentMessage.react(NONE_OF_THAT)
 | 
			
		||||
 | 
			
		||||
        // sentMessage.pin() //todo: uncomment when bot has permission to pin messages. Also update closepoll.ts to only fetch pinned messages
 | 
			
		||||
    const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(event.guildId)
 | 
			
		||||
    if (!announcementChannel) {
 | 
			
		||||
      logger.error("Could not find announcement channel. Aborting", { guildId: event.guildId, requestId })
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId })
 | 
			
		||||
 | 
			
		||||
    if (!event.scheduledStartAt) {
 | 
			
		||||
      logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", { guildId: event.guildId, requestId })
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    let message = `[Abstimmung] für https://discord.com/events/${event.guildId}/${event.id}\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty ${createDateStringFromEvent(event, event.guildId, requestId)}! Stimme hierunter für den nächsten Film ab!\n`
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < movies.length; i++) {
 | 
			
		||||
      message = message.concat(Emotes[i]).concat(": ").concat(movies[i]).concat("\n")
 | 
			
		||||
    }
 | 
			
		||||
    message = message.concat(NONE_OF_THAT).concat(": Wenn dir nichts davon gefällt.")
 | 
			
		||||
 | 
			
		||||
    const options: MessageCreateOptions = {
 | 
			
		||||
      allowedMentions: { parse: ["roles"] },
 | 
			
		||||
      content: message,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const sentMessage: Message<true> = await (await announcementChannel.fetch()).send(options)
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < movies.length; i++) {
 | 
			
		||||
      sentMessage.react(Emotes[i])
 | 
			
		||||
    }
 | 
			
		||||
    sentMessage.react(NONE_OF_THAT)
 | 
			
		||||
 | 
			
		||||
    // sentMessage.pin() //todo: uncomment when bot has permission to pin messages. Also update closepoll.ts to only fetch pinned messages
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,46 +7,46 @@ import { logger } from "../logger";
 | 
			
		||||
export const name = 'guildScheduledEventUpdate'
 | 
			
		||||
 | 
			
		||||
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
 | 
			
		||||
    const requestId = uuid()
 | 
			
		||||
    try {
 | 
			
		||||
        if (!newEvent.guild) {
 | 
			
		||||
            logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        const guildId = newEvent.guildId
 | 
			
		||||
 | 
			
		||||
        if (newEvent.description?.toLowerCase().includes("!wp") && newEvent.status === GuildScheduledEventStatus.Completed) {
 | 
			
		||||
            logger.info("A watchparty ended. Cleaning up announcements!", { guildId, requestId })
 | 
			
		||||
            const announcementChannel = client.getAnnouncementChannelForGuild(newEvent.guild.id)
 | 
			
		||||
            if (!announcementChannel) {
 | 
			
		||||
                logger.error("Could not find announcement channel. Aborting", { guildId: newEvent.guild.id, requestId })
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const events = await newEvent.guild.scheduledEvents.fetch()
 | 
			
		||||
 | 
			
		||||
            const wpAnnouncements = (await announcementChannel.messages.fetch()).filter(message => !message.cleanContent.includes("[initial]"))
 | 
			
		||||
            const announcementsWithoutEvent = filterAnnouncementsByPendingWPs(wpAnnouncements, events)
 | 
			
		||||
            logger.info(`Deleting ${announcementsWithoutEvent.length} announcements.`, {guildId, requestId})
 | 
			
		||||
            announcementsWithoutEvent.forEach(message => message.delete())
 | 
			
		||||
        }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        logger.error(<string>error, { guildId: newEvent.guildId, requestId })
 | 
			
		||||
  const requestId = uuid()
 | 
			
		||||
  try {
 | 
			
		||||
    if (!newEvent.guild) {
 | 
			
		||||
      logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    const guildId = newEvent.guildId
 | 
			
		||||
 | 
			
		||||
    if (newEvent.description?.toLowerCase().includes("!wp") && newEvent.status === GuildScheduledEventStatus.Completed) {
 | 
			
		||||
      logger.info("A watchparty ended. Cleaning up announcements!", { guildId, requestId })
 | 
			
		||||
      const announcementChannel = client.getAnnouncementChannelForGuild(newEvent.guild.id)
 | 
			
		||||
      if (!announcementChannel) {
 | 
			
		||||
        logger.error("Could not find announcement channel. Aborting", { guildId: newEvent.guild.id, requestId })
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const events = await newEvent.guild.scheduledEvents.fetch()
 | 
			
		||||
 | 
			
		||||
      const wpAnnouncements = (await announcementChannel.messages.fetch()).filter(message => !message.cleanContent.includes("[initial]"))
 | 
			
		||||
      const announcementsWithoutEvent = filterAnnouncementsByPendingWPs(wpAnnouncements, events)
 | 
			
		||||
      logger.info(`Deleting ${announcementsWithoutEvent.length} announcements.`, { guildId, requestId })
 | 
			
		||||
      announcementsWithoutEvent.forEach(message => message.delete())
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    logger.error(<string>error, { guildId: newEvent.guildId, requestId })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function filterAnnouncementsByPendingWPs(messages: Collection<string, Message<true>>, events: Collection<string, GuildScheduledEvent<GuildScheduledEventStatus>>): Message<true>[] {
 | 
			
		||||
    const filteredMessages: Message<true>[] = []
 | 
			
		||||
    for (const message of messages.values()) {
 | 
			
		||||
        let foundEventForMessage = false
 | 
			
		||||
        for (const event of events.values()) {
 | 
			
		||||
            if (message.cleanContent.includes(event.id)) { //announcement always has eventid because of eventbox
 | 
			
		||||
                foundEventForMessage = true
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if(!foundEventForMessage){
 | 
			
		||||
            filteredMessages.push(message)
 | 
			
		||||
        }
 | 
			
		||||
  const filteredMessages: Message<true>[] = []
 | 
			
		||||
  for (const message of messages.values()) {
 | 
			
		||||
    let foundEventForMessage = false
 | 
			
		||||
    for (const event of events.values()) {
 | 
			
		||||
      if (message.cleanContent.includes(event.id)) { //announcement always has eventid because of eventbox
 | 
			
		||||
        foundEventForMessage = true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return filteredMessages
 | 
			
		||||
    if (!foundEventForMessage) {
 | 
			
		||||
      filteredMessages.push(message)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return filteredMessages
 | 
			
		||||
}
 | 
			
		||||
@@ -9,51 +9,51 @@ export const name = 'voiceStateUpdate'
 | 
			
		||||
 | 
			
		||||
export async function execute(oldState: VoiceState, newState: VoiceState) {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        logger.info(JSON.stringify(newState, null, 2))
 | 
			
		||||
        //ignore events like mute/unmute
 | 
			
		||||
        if(newState.channel?.id === oldState.channel?.id) {
 | 
			
		||||
            logger.info("Not handling VoiceState event because channelid of old and new was the same (i.e. mute/unmute event)")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const scheduledEvents = (await newState.guild.scheduledEvents.fetch())
 | 
			
		||||
            .filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive())
 | 
			
		||||
            .map((key) => key)
 | 
			
		||||
 | 
			
		||||
        const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({withMember: true}))))
 | 
			
		||||
 | 
			
		||||
        //Dont handle users, that are already subscribed to the event. We only want to handle unsubscribed users here
 | 
			
		||||
        let userFound = false;
 | 
			
		||||
        scheduledEventUsers.forEach(collection => {
 | 
			
		||||
            collection.each(key => {
 | 
			
		||||
                logger.info(JSON.stringify(key, null, 2))
 | 
			
		||||
                if(key.member.user.id === newState.member?.user.id)
 | 
			
		||||
                    userFound = true;
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
        if(userFound) {
 | 
			
		||||
            logger.info(`Not handling VoiceState event because user was already subscribed and got an account from there. User: ${JSON.stringify(newState.member, null, 2)}`)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (scheduledEvents.find(event => event.channelId === newState.channelId)) {
 | 
			
		||||
            if(newState.member){
 | 
			
		||||
                logger.info("YO! Da ist jemand dem Channel mit dem Event beigetreten, ich kümmer mich mal um nen Account!")
 | 
			
		||||
                const result = await jellyfinHandler.upsertUser(newState.member, "TEMPORARY", uuid())
 | 
			
		||||
                if (result === UserUpsertResult.created) {
 | 
			
		||||
                    newState.member.createDM().then(channel => channel.send(`Hey! Du bist unserer Watchparty beigetreten, ich hab dir gerade die Zugangsdaten für den Mediaserver geschickt!`))
 | 
			
		||||
                } else {
 | 
			
		||||
                    newState.member.createDM().then(channel => channel.send(`Hey! Du bist unserer Watchparty beigetreten aber du hast bereits einen Account. Falls du ein neues Passwort brauchst nutze /reset_passwort!`))
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                logger.error("WTF? Expected Member?? When doing things")
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            logger.info("VoiceState channelId was not the id of any channel with events")
 | 
			
		||||
        }
 | 
			
		||||
    }catch(error){
 | 
			
		||||
        logger.error(error)
 | 
			
		||||
  try {
 | 
			
		||||
    logger.info(JSON.stringify(newState, null, 2))
 | 
			
		||||
    //ignore events like mute/unmute
 | 
			
		||||
    if (newState.channel?.id === oldState.channel?.id) {
 | 
			
		||||
      logger.info("Not handling VoiceState event because channelid of old and new was the same (i.e. mute/unmute event)")
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const scheduledEvents = (await newState.guild.scheduledEvents.fetch())
 | 
			
		||||
      .filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive())
 | 
			
		||||
      .map((key) => key)
 | 
			
		||||
 | 
			
		||||
    const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({ withMember: true }))))
 | 
			
		||||
 | 
			
		||||
    //Dont handle users, that are already subscribed to the event. We only want to handle unsubscribed users here
 | 
			
		||||
    let userFound = false;
 | 
			
		||||
    scheduledEventUsers.forEach(collection => {
 | 
			
		||||
      collection.each(key => {
 | 
			
		||||
        logger.info(JSON.stringify(key, null, 2))
 | 
			
		||||
        if (key.member.user.id === newState.member?.user.id)
 | 
			
		||||
          userFound = true;
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
    if (userFound) {
 | 
			
		||||
      logger.info(`Not handling VoiceState event because user was already subscribed and got an account from there. User: ${JSON.stringify(newState.member, null, 2)}`)
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if (scheduledEvents.find(event => event.channelId === newState.channelId)) {
 | 
			
		||||
      if (newState.member) {
 | 
			
		||||
        logger.info("YO! Da ist jemand dem Channel mit dem Event beigetreten, ich kümmer mich mal um nen Account!")
 | 
			
		||||
        const result = await jellyfinHandler.upsertUser(newState.member, "TEMPORARY", uuid())
 | 
			
		||||
        if (result === UserUpsertResult.created) {
 | 
			
		||||
          newState.member.createDM().then(channel => channel.send(`Hey! Du bist unserer Watchparty beigetreten, ich hab dir gerade die Zugangsdaten für den Mediaserver geschickt!`))
 | 
			
		||||
        } else {
 | 
			
		||||
          newState.member.createDM().then(channel => channel.send(`Hey! Du bist unserer Watchparty beigetreten aber du hast bereits einen Account. Falls du ein neues Passwort brauchst nutze /reset_passwort!`))
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        logger.error("WTF? Expected Member?? When doing things")
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.info("VoiceState channelId was not the id of any channel with events")
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    logger.error(error)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -8,51 +8,51 @@ import { logger } from "../logger";
 | 
			
		||||
export const name = 'guildScheduledEventUpdate'
 | 
			
		||||
 | 
			
		||||
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
 | 
			
		||||
    try {
 | 
			
		||||
        const requestId = uuid()
 | 
			
		||||
        // logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`, { guildId: newEvent.guildId, requestId })
 | 
			
		||||
        if (!newEvent.guild) {
 | 
			
		||||
            logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newEvent.description?.toLowerCase().includes("!wp") && [GuildScheduledEventStatus.Active, GuildScheduledEventStatus.Completed].includes(newEvent.status)) {
 | 
			
		||||
            const roles = getGuildSpecificTriggerRoleId().map((key, value) => value)
 | 
			
		||||
            const eventMembers = (await newEvent.fetchSubscribers({ withMember: true })).filter(member => !member.member.roles.cache.hasAny(...roles)).map((value) => value.member)
 | 
			
		||||
            const channelMembers = newEvent.channel?.members.filter(member => !member.roles.cache.hasAny(...roles)).map((value) => value)
 | 
			
		||||
            const allMembers = eventMembers.concat(channelMembers ?? [])
 | 
			
		||||
 | 
			
		||||
            const members: GuildMember[] = []
 | 
			
		||||
            for (const member of allMembers) {
 | 
			
		||||
                if (!members.find(x => x.id == member.id))
 | 
			
		||||
                    members.push(member)
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            if (newEvent.status === GuildScheduledEventStatus.Active)
 | 
			
		||||
                createJFUsers(members, newEvent.name, requestId)
 | 
			
		||||
            else {
 | 
			
		||||
 | 
			
		||||
                members.forEach(member => {
 | 
			
		||||
                    member.createDM().then(channel => channel.send(`Die Watchparty ist vorbei, dein Account wurde wieder gelöscht. Wenn du einen permanenten Account haben möchtest, melde dich bei Samantha oder Marukus.`))
 | 
			
		||||
                })
 | 
			
		||||
                deleteJFUsers(newEvent.guildId, requestId)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        logger.error(error)
 | 
			
		||||
  try {
 | 
			
		||||
    const requestId = uuid()
 | 
			
		||||
    // logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`, { guildId: newEvent.guildId, requestId })
 | 
			
		||||
    if (!newEvent.guild) {
 | 
			
		||||
      logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (newEvent.description?.toLowerCase().includes("!wp") && [GuildScheduledEventStatus.Active, GuildScheduledEventStatus.Completed].includes(newEvent.status)) {
 | 
			
		||||
      const roles = getGuildSpecificTriggerRoleId().map((key, value) => value)
 | 
			
		||||
      const eventMembers = (await newEvent.fetchSubscribers({ withMember: true })).filter(member => !member.member.roles.cache.hasAny(...roles)).map((value) => value.member)
 | 
			
		||||
      const channelMembers = newEvent.channel?.members.filter(member => !member.roles.cache.hasAny(...roles)).map((value) => value)
 | 
			
		||||
      const allMembers = eventMembers.concat(channelMembers ?? [])
 | 
			
		||||
 | 
			
		||||
      const members: GuildMember[] = []
 | 
			
		||||
      for (const member of allMembers) {
 | 
			
		||||
        if (!members.find(x => x.id == member.id))
 | 
			
		||||
          members.push(member)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      if (newEvent.status === GuildScheduledEventStatus.Active)
 | 
			
		||||
        createJFUsers(members, newEvent.name, requestId)
 | 
			
		||||
      else {
 | 
			
		||||
 | 
			
		||||
        members.forEach(member => {
 | 
			
		||||
          member.createDM().then(channel => channel.send(`Die Watchparty ist vorbei, dein Account wurde wieder gelöscht. Wenn du einen permanenten Account haben möchtest, melde dich bei Samantha oder Marukus.`))
 | 
			
		||||
        })
 | 
			
		||||
        deleteJFUsers(newEvent.guildId, requestId)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    logger.error(error)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function createJFUsers(members: GuildMember[], movieName: string, requestId?: string) {
 | 
			
		||||
    logger.info(`Creating users for: \n ${JSON.stringify(members, null, 2)}`)
 | 
			
		||||
    members.forEach(member => {
 | 
			
		||||
        member.createDM().then(channel => channel.send(`Hey! Du hast dich für die Watchparty von ${movieName} angemeldet! Es geht gleich los!`))
 | 
			
		||||
        jellyfinHandler.upsertUser(member, "TEMPORARY", requestId)
 | 
			
		||||
    })
 | 
			
		||||
  logger.info(`Creating users for: \n ${JSON.stringify(members, null, 2)}`)
 | 
			
		||||
  members.forEach(member => {
 | 
			
		||||
    member.createDM().then(channel => channel.send(`Hey! Du hast dich für die Watchparty von ${movieName} angemeldet! Es geht gleich los!`))
 | 
			
		||||
    jellyfinHandler.upsertUser(member, "TEMPORARY", requestId)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function deleteJFUsers(guildId: string, requestId?: string) {
 | 
			
		||||
    logger.info(`Watchparty ended, deleting tmp users`, { guildId, requestId })
 | 
			
		||||
    jellyfinHandler.purge(guildId, requestId)
 | 
			
		||||
  logger.info(`Watchparty ended, deleting tmp users`, { guildId, requestId })
 | 
			
		||||
  jellyfinHandler.purge(guildId, requestId)
 | 
			
		||||
}
 | 
			
		||||
@@ -2,5 +2,5 @@ import { Message } from "discord.js"
 | 
			
		||||
 | 
			
		||||
export const name = 'messageCreate'
 | 
			
		||||
export function execute(message: Message) {
 | 
			
		||||
	console.log(`${JSON.stringify(message)} has been created`)
 | 
			
		||||
  console.log(`${JSON.stringify(message)} has been created`)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,23 @@
 | 
			
		||||
import { format, isToday, toDate } from "date-fns";
 | 
			
		||||
import {utcToZonedTime} from "date-fns-tz"
 | 
			
		||||
import { utcToZonedTime } from "date-fns-tz"
 | 
			
		||||
import { GuildScheduledEvent } from "discord.js";
 | 
			
		||||
import { logger } from "../logger";
 | 
			
		||||
import de from "date-fns/locale/de";
 | 
			
		||||
 | 
			
		||||
export function createDateStringFromEvent(event: GuildScheduledEvent, requestId: string, guildId?: string): string {
 | 
			
		||||
    if(!event.scheduledStartAt) {
 | 
			
		||||
        logger.error("Event has no start. Cannot create dateString.", {guildId, requestId})
 | 
			
		||||
        return `"habe keinen Startzeitpunkt ermitteln können"`
 | 
			
		||||
    }
 | 
			
		||||
  if (!event.scheduledStartAt) {
 | 
			
		||||
    logger.error("Event has no start. Cannot create dateString.", { guildId, requestId })
 | 
			
		||||
    return `"habe keinen Startzeitpunkt ermitteln können"`
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const timeZone = 'Europe/Berlin'
 | 
			
		||||
    const zonedDateTime = utcToZonedTime(event.scheduledStartAt, timeZone)
 | 
			
		||||
    const time = format(zonedDateTime, "HH:mm", {locale: de})
 | 
			
		||||
    
 | 
			
		||||
    if(isToday(zonedDateTime)) {
 | 
			
		||||
        return `heute um ${time}`
 | 
			
		||||
    }
 | 
			
		||||
  const timeZone = 'Europe/Berlin'
 | 
			
		||||
  const zonedDateTime = utcToZonedTime(event.scheduledStartAt, timeZone)
 | 
			
		||||
  const time = format(zonedDateTime, "HH:mm", { locale: de })
 | 
			
		||||
 | 
			
		||||
    const date = format(zonedDateTime, "eeee dd.MM", {locale: de})
 | 
			
		||||
    return `am ${date} um ${time}`
 | 
			
		||||
  if (isToday(zonedDateTime)) {
 | 
			
		||||
    return `heute um ${time}`
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const date = format(zonedDateTime, "eeee dd.MM", { locale: de })
 | 
			
		||||
  return `am ${date} um ${time}`
 | 
			
		||||
}
 | 
			
		||||
@@ -64,7 +64,7 @@ export class JellyfinHandler {
 | 
			
		||||
    logger.debug(JSON.stringify(req), { requestId, guildId })
 | 
			
		||||
    const createResult = await this.userApi.createUserByName(req)
 | 
			
		||||
    if (createResult) {
 | 
			
		||||
      if(createResult.policy) {
 | 
			
		||||
      if (createResult.policy) {
 | 
			
		||||
        this.setUserPermissions(createResult, requestId, guildId)
 | 
			
		||||
      }
 | 
			
		||||
      (await discordUser.createDM()).send(`Ich hab dir mal nen Account angelegt :)\nDein Username ist ${createResult.name}, dein Password ist "${req.createUserByNameRequest.password}"!`)
 | 
			
		||||
@@ -74,8 +74,8 @@ export class JellyfinHandler {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async setUserPermissions(user: UserDto, requestId: string, guildId?: string) {
 | 
			
		||||
    if(!user.policy || !user.id) {
 | 
			
		||||
      logger.error(`Cannot update user policy. User ${user.name} has no policy to modify`, {guildId, requestId}) 
 | 
			
		||||
    if (!user.policy || !user.id) {
 | 
			
		||||
      logger.error(`Cannot update user policy. User ${user.name} has no policy to modify`, { guildId, requestId })
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    user.policy.enableVideoPlaybackTranscoding = false
 | 
			
		||||
@@ -273,7 +273,7 @@ export class JellyfinHandler {
 | 
			
		||||
    let movieCount = 0
 | 
			
		||||
    let movieNames: string[]
 | 
			
		||||
    do {
 | 
			
		||||
      movieNames = (await this.getRandomMovies(count, guildId, requestId)).filter(movie => movie.name && movie.name.length > 0).map(movie => <string> movie.name)
 | 
			
		||||
      movieNames = (await this.getRandomMovies(count, guildId, requestId)).filter(movie => movie.name && movie.name.length > 0).map(movie => <string>movie.name)
 | 
			
		||||
      movieCount = movieNames.length
 | 
			
		||||
    } while (movieCount < count)
 | 
			
		||||
    return movieNames
 | 
			
		||||
 
 | 
			
		||||
@@ -16,72 +16,72 @@
 | 
			
		||||
export const BASE_PATH = "http://localhost".replace(/\/+$/, "");
 | 
			
		||||
 | 
			
		||||
export interface ConfigurationParameters {
 | 
			
		||||
    basePath?: string; // override base path
 | 
			
		||||
    fetchApi?: FetchAPI; // override for fetch implementation
 | 
			
		||||
    middleware?: Middleware[]; // middleware to apply before/after fetch requests
 | 
			
		||||
    queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings
 | 
			
		||||
    username?: string; // parameter for basic security
 | 
			
		||||
    password?: string; // parameter for basic security
 | 
			
		||||
    apiKey?: string | ((name: string) => string); // parameter for apiKey security
 | 
			
		||||
    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string | Promise<string>); // parameter for oauth2 security
 | 
			
		||||
    headers?: HTTPHeaders; //header params we want to use on every request
 | 
			
		||||
    credentials?: RequestCredentials; //value for the credentials param we want to use on each request
 | 
			
		||||
  basePath?: string; // override base path
 | 
			
		||||
  fetchApi?: FetchAPI; // override for fetch implementation
 | 
			
		||||
  middleware?: Middleware[]; // middleware to apply before/after fetch requests
 | 
			
		||||
  queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings
 | 
			
		||||
  username?: string; // parameter for basic security
 | 
			
		||||
  password?: string; // parameter for basic security
 | 
			
		||||
  apiKey?: string | ((name: string) => string); // parameter for apiKey security
 | 
			
		||||
  accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string | Promise<string>); // parameter for oauth2 security
 | 
			
		||||
  headers?: HTTPHeaders; //header params we want to use on every request
 | 
			
		||||
  credentials?: RequestCredentials; //value for the credentials param we want to use on each request
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Configuration {
 | 
			
		||||
    constructor(private configuration: ConfigurationParameters = {}) {}
 | 
			
		||||
  constructor(private configuration: ConfigurationParameters = {}) { }
 | 
			
		||||
 | 
			
		||||
    set config(configuration: Configuration) {
 | 
			
		||||
        this.configuration = configuration;
 | 
			
		||||
    }
 | 
			
		||||
  set config(configuration: Configuration) {
 | 
			
		||||
    this.configuration = configuration;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    get basePath(): string {
 | 
			
		||||
        return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH;
 | 
			
		||||
    }
 | 
			
		||||
  get basePath(): string {
 | 
			
		||||
    return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    get fetchApi(): FetchAPI | undefined {
 | 
			
		||||
        return this.configuration.fetchApi;
 | 
			
		||||
    }
 | 
			
		||||
  get fetchApi(): FetchAPI | undefined {
 | 
			
		||||
    return this.configuration.fetchApi;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    get middleware(): Middleware[] {
 | 
			
		||||
        return this.configuration.middleware || [];
 | 
			
		||||
    }
 | 
			
		||||
  get middleware(): Middleware[] {
 | 
			
		||||
    return this.configuration.middleware || [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    get queryParamsStringify(): (params: HTTPQuery) => string {
 | 
			
		||||
        return this.configuration.queryParamsStringify || querystring;
 | 
			
		||||
    }
 | 
			
		||||
  get queryParamsStringify(): (params: HTTPQuery) => string {
 | 
			
		||||
    return this.configuration.queryParamsStringify || querystring;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    get username(): string | undefined {
 | 
			
		||||
        return this.configuration.username;
 | 
			
		||||
    }
 | 
			
		||||
  get username(): string | undefined {
 | 
			
		||||
    return this.configuration.username;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    get password(): string | undefined {
 | 
			
		||||
        return this.configuration.password;
 | 
			
		||||
    }
 | 
			
		||||
  get password(): string | undefined {
 | 
			
		||||
    return this.configuration.password;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    get apiKey(): ((name: string) => string) | undefined {
 | 
			
		||||
        const apiKey = this.configuration.apiKey;
 | 
			
		||||
        if (apiKey) {
 | 
			
		||||
            return typeof apiKey === 'function' ? apiKey : () => apiKey;
 | 
			
		||||
        }
 | 
			
		||||
        return undefined;
 | 
			
		||||
  get apiKey(): ((name: string) => string) | undefined {
 | 
			
		||||
    const apiKey = this.configuration.apiKey;
 | 
			
		||||
    if (apiKey) {
 | 
			
		||||
      return typeof apiKey === 'function' ? apiKey : () => apiKey;
 | 
			
		||||
    }
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    get accessToken(): ((name?: string, scopes?: string[]) => string | Promise<string>) | undefined {
 | 
			
		||||
        const accessToken = this.configuration.accessToken;
 | 
			
		||||
        if (accessToken) {
 | 
			
		||||
            return typeof accessToken === 'function' ? accessToken : async () => accessToken;
 | 
			
		||||
        }
 | 
			
		||||
        return undefined;
 | 
			
		||||
  get accessToken(): ((name?: string, scopes?: string[]) => string | Promise<string>) | undefined {
 | 
			
		||||
    const accessToken = this.configuration.accessToken;
 | 
			
		||||
    if (accessToken) {
 | 
			
		||||
      return typeof accessToken === 'function' ? accessToken : async () => accessToken;
 | 
			
		||||
    }
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    get headers(): HTTPHeaders | undefined {
 | 
			
		||||
        return this.configuration.headers;
 | 
			
		||||
    }
 | 
			
		||||
  get headers(): HTTPHeaders | undefined {
 | 
			
		||||
    return this.configuration.headers;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    get credentials(): RequestCredentials | undefined {
 | 
			
		||||
        return this.configuration.credentials;
 | 
			
		||||
    }
 | 
			
		||||
  get credentials(): RequestCredentials | undefined {
 | 
			
		||||
    return this.configuration.credentials;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const DefaultConfig = new Configuration();
 | 
			
		||||
@@ -91,192 +91,192 @@ export const DefaultConfig = new Configuration();
 | 
			
		||||
 */
 | 
			
		||||
export class BaseAPI {
 | 
			
		||||
 | 
			
		||||
	 private static readonly jsonRegex = new RegExp('^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i');
 | 
			
		||||
    private middleware: Middleware[];
 | 
			
		||||
  private static readonly jsonRegex = new RegExp('^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i');
 | 
			
		||||
  private middleware: Middleware[];
 | 
			
		||||
 | 
			
		||||
    constructor(protected configuration = DefaultConfig) {
 | 
			
		||||
        this.middleware = configuration.middleware;
 | 
			
		||||
  constructor(protected configuration = DefaultConfig) {
 | 
			
		||||
    this.middleware = configuration.middleware;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  withMiddleware<T extends BaseAPI>(this: T, ...middlewares: Middleware[]) {
 | 
			
		||||
    const next = this.clone<T>();
 | 
			
		||||
    next.middleware = next.middleware.concat(...middlewares);
 | 
			
		||||
    return next;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  withPreMiddleware<T extends BaseAPI>(this: T, ...preMiddlewares: Array<Middleware['pre']>) {
 | 
			
		||||
    const middlewares = preMiddlewares.map((pre) => ({ pre }));
 | 
			
		||||
    return this.withMiddleware<T>(...middlewares);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  withPostMiddleware<T extends BaseAPI>(this: T, ...postMiddlewares: Array<Middleware['post']>) {
 | 
			
		||||
    const middlewares = postMiddlewares.map((post) => ({ post }));
 | 
			
		||||
    return this.withMiddleware<T>(...middlewares);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check if the given MIME is a JSON MIME.
 | 
			
		||||
   * JSON MIME examples:
 | 
			
		||||
   *   application/json
 | 
			
		||||
   *   application/json; charset=UTF8
 | 
			
		||||
   *   APPLICATION/JSON
 | 
			
		||||
   *   application/vnd.company+json
 | 
			
		||||
   * @param mime - MIME (Multipurpose Internet Mail Extensions)
 | 
			
		||||
   * @return True if the given MIME is JSON, false otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  protected isJsonMime(mime: string | null | undefined): boolean {
 | 
			
		||||
    if (!mime) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return BaseAPI.jsonRegex.test(mime);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise<Response> {
 | 
			
		||||
    const { url, init } = await this.createFetchParams(context, initOverrides);
 | 
			
		||||
    const response = await this.fetchApi(url, init);
 | 
			
		||||
    if (response && (response.status >= 200 && response.status < 300)) {
 | 
			
		||||
      return response;
 | 
			
		||||
    }
 | 
			
		||||
    throw new ResponseError(response, 'Response returned an error code');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) {
 | 
			
		||||
    let url = this.configuration.basePath + context.path;
 | 
			
		||||
    if (context.query !== undefined && Object.keys(context.query).length !== 0) {
 | 
			
		||||
      // only add the querystring to the URL if there are query parameters.
 | 
			
		||||
      // this is done to avoid urls ending with a "?" character which buggy webservers
 | 
			
		||||
      // do not handle correctly sometimes.
 | 
			
		||||
      url += '?' + this.configuration.queryParamsStringify(context.query);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    withMiddleware<T extends BaseAPI>(this: T, ...middlewares: Middleware[]) {
 | 
			
		||||
        const next = this.clone<T>();
 | 
			
		||||
        next.middleware = next.middleware.concat(...middlewares);
 | 
			
		||||
        return next;
 | 
			
		||||
    }
 | 
			
		||||
    const headers = Object.assign({}, this.configuration.headers, context.headers);
 | 
			
		||||
    Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {});
 | 
			
		||||
 | 
			
		||||
    withPreMiddleware<T extends BaseAPI>(this: T, ...preMiddlewares: Array<Middleware['pre']>) {
 | 
			
		||||
        const middlewares = preMiddlewares.map((pre) => ({ pre }));
 | 
			
		||||
        return this.withMiddleware<T>(...middlewares);
 | 
			
		||||
    }
 | 
			
		||||
    const initOverrideFn =
 | 
			
		||||
      typeof initOverrides === "function"
 | 
			
		||||
        ? initOverrides
 | 
			
		||||
        : async () => initOverrides;
 | 
			
		||||
 | 
			
		||||
    withPostMiddleware<T extends BaseAPI>(this: T, ...postMiddlewares: Array<Middleware['post']>) {
 | 
			
		||||
        const middlewares = postMiddlewares.map((post) => ({ post }));
 | 
			
		||||
        return this.withMiddleware<T>(...middlewares);
 | 
			
		||||
    }
 | 
			
		||||
    const initParams = {
 | 
			
		||||
      method: context.method,
 | 
			
		||||
      headers,
 | 
			
		||||
      body: context.body,
 | 
			
		||||
      credentials: this.configuration.credentials,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the given MIME is a JSON MIME.
 | 
			
		||||
     * JSON MIME examples:
 | 
			
		||||
     *   application/json
 | 
			
		||||
     *   application/json; charset=UTF8
 | 
			
		||||
     *   APPLICATION/JSON
 | 
			
		||||
     *   application/vnd.company+json
 | 
			
		||||
     * @param mime - MIME (Multipurpose Internet Mail Extensions)
 | 
			
		||||
     * @return True if the given MIME is JSON, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    protected isJsonMime(mime: string | null | undefined): boolean {
 | 
			
		||||
        if (!mime) {
 | 
			
		||||
            return false;
 | 
			
		||||
    const overriddenInit: RequestInit = {
 | 
			
		||||
      ...initParams,
 | 
			
		||||
      ...(await initOverrideFn({
 | 
			
		||||
        init: initParams,
 | 
			
		||||
        context,
 | 
			
		||||
      }))
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const init: RequestInit = {
 | 
			
		||||
      ...overriddenInit,
 | 
			
		||||
      body:
 | 
			
		||||
        isFormData(overriddenInit.body) ||
 | 
			
		||||
          overriddenInit.body instanceof URLSearchParams ||
 | 
			
		||||
          isBlob(overriddenInit.body)
 | 
			
		||||
          ? overriddenInit.body
 | 
			
		||||
          : JSON.stringify(overriddenInit.body),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return { url, init };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fetchApi = async (url: string, init: RequestInit) => {
 | 
			
		||||
    let fetchParams = { url, init };
 | 
			
		||||
    for (const middleware of this.middleware) {
 | 
			
		||||
      if (middleware.pre) {
 | 
			
		||||
        fetchParams = await middleware.pre({
 | 
			
		||||
          fetch: this.fetchApi,
 | 
			
		||||
          ...fetchParams,
 | 
			
		||||
        }) || fetchParams;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    let response: Response | undefined = undefined;
 | 
			
		||||
    try {
 | 
			
		||||
      response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      for (const middleware of this.middleware) {
 | 
			
		||||
        if (middleware.onError) {
 | 
			
		||||
          response = await middleware.onError({
 | 
			
		||||
            fetch: this.fetchApi,
 | 
			
		||||
            url: fetchParams.url,
 | 
			
		||||
            init: fetchParams.init,
 | 
			
		||||
            error: e,
 | 
			
		||||
            response: response ? response.clone() : undefined,
 | 
			
		||||
          }) || response;
 | 
			
		||||
        }
 | 
			
		||||
        return BaseAPI.jsonRegex.test(mime);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise<Response> {
 | 
			
		||||
        const { url, init } = await this.createFetchParams(context, initOverrides);
 | 
			
		||||
        const response = await this.fetchApi(url, init);
 | 
			
		||||
        if (response && (response.status >= 200 && response.status < 300)) {
 | 
			
		||||
            return response;
 | 
			
		||||
      }
 | 
			
		||||
      if (response === undefined) {
 | 
			
		||||
        if (e instanceof Error) {
 | 
			
		||||
          throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response');
 | 
			
		||||
        } else {
 | 
			
		||||
          throw e;
 | 
			
		||||
        }
 | 
			
		||||
        throw new ResponseError(response, 'Response returned an error code');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) {
 | 
			
		||||
        let url = this.configuration.basePath + context.path;
 | 
			
		||||
        if (context.query !== undefined && Object.keys(context.query).length !== 0) {
 | 
			
		||||
            // only add the querystring to the URL if there are query parameters.
 | 
			
		||||
            // this is done to avoid urls ending with a "?" character which buggy webservers
 | 
			
		||||
            // do not handle correctly sometimes.
 | 
			
		||||
            url += '?' + this.configuration.queryParamsStringify(context.query);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const headers = Object.assign({}, this.configuration.headers, context.headers);
 | 
			
		||||
        Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {});
 | 
			
		||||
 | 
			
		||||
        const initOverrideFn =
 | 
			
		||||
            typeof initOverrides === "function"
 | 
			
		||||
                ? initOverrides
 | 
			
		||||
                : async () => initOverrides;
 | 
			
		||||
 | 
			
		||||
        const initParams = {
 | 
			
		||||
            method: context.method,
 | 
			
		||||
            headers,
 | 
			
		||||
            body: context.body,
 | 
			
		||||
            credentials: this.configuration.credentials,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const overriddenInit: RequestInit = {
 | 
			
		||||
            ...initParams,
 | 
			
		||||
            ...(await initOverrideFn({
 | 
			
		||||
                init: initParams,
 | 
			
		||||
                context,
 | 
			
		||||
            }))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const init: RequestInit = {
 | 
			
		||||
            ...overriddenInit,
 | 
			
		||||
            body:
 | 
			
		||||
                isFormData(overriddenInit.body) ||
 | 
			
		||||
                overriddenInit.body instanceof URLSearchParams ||
 | 
			
		||||
                isBlob(overriddenInit.body)
 | 
			
		||||
                    ? overriddenInit.body
 | 
			
		||||
                    : JSON.stringify(overriddenInit.body),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return { url, init };
 | 
			
		||||
    for (const middleware of this.middleware) {
 | 
			
		||||
      if (middleware.post) {
 | 
			
		||||
        response = await middleware.post({
 | 
			
		||||
          fetch: this.fetchApi,
 | 
			
		||||
          url: fetchParams.url,
 | 
			
		||||
          init: fetchParams.init,
 | 
			
		||||
          response: response.clone(),
 | 
			
		||||
        }) || response;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    private fetchApi = async (url: string, init: RequestInit) => {
 | 
			
		||||
        let fetchParams = { url, init };
 | 
			
		||||
        for (const middleware of this.middleware) {
 | 
			
		||||
            if (middleware.pre) {
 | 
			
		||||
                fetchParams = await middleware.pre({
 | 
			
		||||
                    fetch: this.fetchApi,
 | 
			
		||||
                    ...fetchParams,
 | 
			
		||||
                }) || fetchParams;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        let response: Response | undefined = undefined;
 | 
			
		||||
        try {
 | 
			
		||||
            response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            for (const middleware of this.middleware) {
 | 
			
		||||
                if (middleware.onError) {
 | 
			
		||||
                    response = await middleware.onError({
 | 
			
		||||
                        fetch: this.fetchApi,
 | 
			
		||||
                        url: fetchParams.url,
 | 
			
		||||
                        init: fetchParams.init,
 | 
			
		||||
                        error: e,
 | 
			
		||||
                        response: response ? response.clone() : undefined,
 | 
			
		||||
                    }) || response;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (response === undefined) {
 | 
			
		||||
              if (e instanceof Error) {
 | 
			
		||||
                throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response');
 | 
			
		||||
              } else {
 | 
			
		||||
                throw e;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        for (const middleware of this.middleware) {
 | 
			
		||||
            if (middleware.post) {
 | 
			
		||||
                response = await middleware.post({
 | 
			
		||||
                    fetch: this.fetchApi,
 | 
			
		||||
                    url: fetchParams.url,
 | 
			
		||||
                    init: fetchParams.init,
 | 
			
		||||
                    response: response.clone(),
 | 
			
		||||
                }) || response;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a shallow clone of `this` by constructing a new instance
 | 
			
		||||
     * and then shallow cloning data members.
 | 
			
		||||
     */
 | 
			
		||||
    private clone<T extends BaseAPI>(this: T): T {
 | 
			
		||||
        const constructor = this.constructor as any;
 | 
			
		||||
        const next = new constructor(this.configuration);
 | 
			
		||||
        next.middleware = this.middleware.slice();
 | 
			
		||||
        return next;
 | 
			
		||||
    }
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a shallow clone of `this` by constructing a new instance
 | 
			
		||||
   * and then shallow cloning data members.
 | 
			
		||||
   */
 | 
			
		||||
  private clone<T extends BaseAPI>(this: T): T {
 | 
			
		||||
    const constructor = this.constructor as any;
 | 
			
		||||
    const next = new constructor(this.configuration);
 | 
			
		||||
    next.middleware = this.middleware.slice();
 | 
			
		||||
    return next;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function isBlob(value: any): value is Blob {
 | 
			
		||||
    return typeof Blob !== 'undefined' && value instanceof Blob;
 | 
			
		||||
  return typeof Blob !== 'undefined' && value instanceof Blob;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isFormData(value: any): value is FormData {
 | 
			
		||||
    return typeof FormData !== "undefined" && value instanceof FormData;
 | 
			
		||||
  return typeof FormData !== "undefined" && value instanceof FormData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ResponseError extends Error {
 | 
			
		||||
    override name: "ResponseError" = "ResponseError";
 | 
			
		||||
    constructor(public response: Response, msg?: string) {
 | 
			
		||||
        super(msg);
 | 
			
		||||
    }
 | 
			
		||||
  override name: "ResponseError" = "ResponseError";
 | 
			
		||||
  constructor(public response: Response, msg?: string) {
 | 
			
		||||
    super(msg);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class FetchError extends Error {
 | 
			
		||||
    override name: "FetchError" = "FetchError";
 | 
			
		||||
    constructor(public cause: Error, msg?: string) {
 | 
			
		||||
        super(msg);
 | 
			
		||||
    }
 | 
			
		||||
  override name: "FetchError" = "FetchError";
 | 
			
		||||
  constructor(public cause: Error, msg?: string) {
 | 
			
		||||
    super(msg);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class RequiredError extends Error {
 | 
			
		||||
    override name: "RequiredError" = "RequiredError";
 | 
			
		||||
    constructor(public field: string, msg?: string) {
 | 
			
		||||
        super(msg);
 | 
			
		||||
    }
 | 
			
		||||
  override name: "RequiredError" = "RequiredError";
 | 
			
		||||
  constructor(public field: string, msg?: string) {
 | 
			
		||||
    super(msg);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const COLLECTION_FORMATS = {
 | 
			
		||||
    csv: ",",
 | 
			
		||||
    ssv: " ",
 | 
			
		||||
    tsv: "\t",
 | 
			
		||||
    pipes: "|",
 | 
			
		||||
  csv: ",",
 | 
			
		||||
  ssv: " ",
 | 
			
		||||
  tsv: "\t",
 | 
			
		||||
  pipes: "|",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type FetchAPI = WindowOrWorkerGlobalScope['fetch'];
 | 
			
		||||
@@ -292,48 +292,48 @@ export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'o
 | 
			
		||||
export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise<RequestInit>
 | 
			
		||||
 | 
			
		||||
export interface FetchParams {
 | 
			
		||||
    url: string;
 | 
			
		||||
    init: RequestInit;
 | 
			
		||||
  url: string;
 | 
			
		||||
  init: RequestInit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RequestOpts {
 | 
			
		||||
    path: string;
 | 
			
		||||
    method: HTTPMethod;
 | 
			
		||||
    headers: HTTPHeaders;
 | 
			
		||||
    query?: HTTPQuery;
 | 
			
		||||
    body?: HTTPBody;
 | 
			
		||||
  path: string;
 | 
			
		||||
  method: HTTPMethod;
 | 
			
		||||
  headers: HTTPHeaders;
 | 
			
		||||
  query?: HTTPQuery;
 | 
			
		||||
  body?: HTTPBody;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function exists(json: any, key: string) {
 | 
			
		||||
    const value = json[key];
 | 
			
		||||
    return value !== null && value !== undefined;
 | 
			
		||||
  const value = json[key];
 | 
			
		||||
  return value !== null && value !== undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function querystring(params: HTTPQuery, prefix: string = ''): string {
 | 
			
		||||
    return Object.keys(params)
 | 
			
		||||
        .map(key => querystringSingleKey(key, params[key], prefix))
 | 
			
		||||
        .filter(part => part.length > 0)
 | 
			
		||||
        .join('&');
 | 
			
		||||
  return Object.keys(params)
 | 
			
		||||
    .map(key => querystringSingleKey(key, params[key], prefix))
 | 
			
		||||
    .filter(part => part.length > 0)
 | 
			
		||||
    .join('&');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array<string | number | null | boolean> | Set<string | number | null | boolean> | HTTPQuery, keyPrefix: string = ''): string {
 | 
			
		||||
    const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key);
 | 
			
		||||
    if (value instanceof Array) {
 | 
			
		||||
        const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue)))
 | 
			
		||||
            .join(`&${encodeURIComponent(fullKey)}=`);
 | 
			
		||||
        return `${encodeURIComponent(fullKey)}=${multiValue}`;
 | 
			
		||||
    }
 | 
			
		||||
    if (value instanceof Set) {
 | 
			
		||||
        const valueAsArray = Array.from(value);
 | 
			
		||||
        return querystringSingleKey(key, valueAsArray, keyPrefix);
 | 
			
		||||
    }
 | 
			
		||||
    if (value instanceof Date) {
 | 
			
		||||
        return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`;
 | 
			
		||||
    }
 | 
			
		||||
    if (value instanceof Object) {
 | 
			
		||||
        return querystring(value as HTTPQuery, fullKey);
 | 
			
		||||
    }
 | 
			
		||||
    return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
 | 
			
		||||
  const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key);
 | 
			
		||||
  if (value instanceof Array) {
 | 
			
		||||
    const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue)))
 | 
			
		||||
      .join(`&${encodeURIComponent(fullKey)}=`);
 | 
			
		||||
    return `${encodeURIComponent(fullKey)}=${multiValue}`;
 | 
			
		||||
  }
 | 
			
		||||
  if (value instanceof Set) {
 | 
			
		||||
    const valueAsArray = Array.from(value);
 | 
			
		||||
    return querystringSingleKey(key, valueAsArray, keyPrefix);
 | 
			
		||||
  }
 | 
			
		||||
  if (value instanceof Date) {
 | 
			
		||||
    return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`;
 | 
			
		||||
  }
 | 
			
		||||
  if (value instanceof Object) {
 | 
			
		||||
    return querystring(value as HTTPQuery, fullKey);
 | 
			
		||||
  }
 | 
			
		||||
  return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function mapValues(data: any, fn: (item: any) => any) {
 | 
			
		||||
@@ -344,82 +344,82 @@ export function mapValues(data: any, fn: (item: any) => any) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function canConsumeForm(consumes: Consume[]): boolean {
 | 
			
		||||
    for (const consume of consumes) {
 | 
			
		||||
        if ('multipart/form-data' === consume.contentType) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
  for (const consume of consumes) {
 | 
			
		||||
    if ('multipart/form-data' === consume.contentType) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Consume {
 | 
			
		||||
    contentType: string;
 | 
			
		||||
  contentType: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RequestContext {
 | 
			
		||||
    fetch: FetchAPI;
 | 
			
		||||
    url: string;
 | 
			
		||||
    init: RequestInit;
 | 
			
		||||
  fetch: FetchAPI;
 | 
			
		||||
  url: string;
 | 
			
		||||
  init: RequestInit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ResponseContext {
 | 
			
		||||
    fetch: FetchAPI;
 | 
			
		||||
    url: string;
 | 
			
		||||
    init: RequestInit;
 | 
			
		||||
    response: Response;
 | 
			
		||||
  fetch: FetchAPI;
 | 
			
		||||
  url: string;
 | 
			
		||||
  init: RequestInit;
 | 
			
		||||
  response: Response;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ErrorContext {
 | 
			
		||||
    fetch: FetchAPI;
 | 
			
		||||
    url: string;
 | 
			
		||||
    init: RequestInit;
 | 
			
		||||
    error: unknown;
 | 
			
		||||
    response?: Response;
 | 
			
		||||
  fetch: FetchAPI;
 | 
			
		||||
  url: string;
 | 
			
		||||
  init: RequestInit;
 | 
			
		||||
  error: unknown;
 | 
			
		||||
  response?: Response;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Middleware {
 | 
			
		||||
    pre?(context: RequestContext): Promise<FetchParams | void>;
 | 
			
		||||
    post?(context: ResponseContext): Promise<Response | void>;
 | 
			
		||||
    onError?(context: ErrorContext): Promise<Response | void>;
 | 
			
		||||
  pre?(context: RequestContext): Promise<FetchParams | void>;
 | 
			
		||||
  post?(context: ResponseContext): Promise<Response | void>;
 | 
			
		||||
  onError?(context: ErrorContext): Promise<Response | void>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ApiResponse<T> {
 | 
			
		||||
    raw: Response;
 | 
			
		||||
    value(): Promise<T>;
 | 
			
		||||
  raw: Response;
 | 
			
		||||
  value(): Promise<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ResponseTransformer<T> {
 | 
			
		||||
    (json: any): T;
 | 
			
		||||
  (json: any): T;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class JSONApiResponse<T> {
 | 
			
		||||
    constructor(public raw: Response, private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue) {}
 | 
			
		||||
  constructor(public raw: Response, private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue) { }
 | 
			
		||||
 | 
			
		||||
    async value(): Promise<T> {
 | 
			
		||||
        return this.transformer(await this.raw.json());
 | 
			
		||||
    }
 | 
			
		||||
  async value(): Promise<T> {
 | 
			
		||||
    return this.transformer(await this.raw.json());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class VoidApiResponse {
 | 
			
		||||
    constructor(public raw: Response) {}
 | 
			
		||||
  constructor(public raw: Response) { }
 | 
			
		||||
 | 
			
		||||
    async value(): Promise<void> {
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
  async value(): Promise<void> {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class BlobApiResponse {
 | 
			
		||||
    constructor(public raw: Response) {}
 | 
			
		||||
  constructor(public raw: Response) { }
 | 
			
		||||
 | 
			
		||||
    async value(): Promise<Blob> {
 | 
			
		||||
        return await this.raw.blob();
 | 
			
		||||
    };
 | 
			
		||||
  async value(): Promise<Blob> {
 | 
			
		||||
    return await this.raw.blob();
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TextApiResponse {
 | 
			
		||||
    constructor(public raw: Response) {}
 | 
			
		||||
  constructor(public raw: Response) { }
 | 
			
		||||
 | 
			
		||||
    async value(): Promise<string> {
 | 
			
		||||
        return await this.raw.text();
 | 
			
		||||
    };
 | 
			
		||||
  async value(): Promise<string> {
 | 
			
		||||
    return await this.raw.text();
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -130,10 +130,10 @@ export class ExtendedClient extends Client {
 | 
			
		||||
    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) {
 | 
			
		||||
      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]"))
 | 
			
		||||
@@ -174,7 +174,7 @@ export class ExtendedClient extends Client {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async startPollCloseBackgroundTasks() {
 | 
			
		||||
    for(const guild of this.guilds.cache) {
 | 
			
		||||
    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,6 +1,6 @@
 | 
			
		||||
import { ApplicationCommandDataResolvable } from "discord.js"
 | 
			
		||||
 | 
			
		||||
export interface RegisterCommandOptions {
 | 
			
		||||
	guildId?: string
 | 
			
		||||
	commands: ApplicationCommandDataResolvable[]
 | 
			
		||||
  guildId?: string
 | 
			
		||||
  commands: ApplicationCommandDataResolvable[]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,16 +2,16 @@ import { PermissionResolvable, ChatInputApplicationCommandData, CommandInteracti
 | 
			
		||||
import { ExtendedClient } from "../structures/client";
 | 
			
		||||
 | 
			
		||||
export interface ExtendedInteraction extends CommandInteraction {
 | 
			
		||||
	member: GuildMember
 | 
			
		||||
  member: GuildMember
 | 
			
		||||
}
 | 
			
		||||
export interface RunOptions {
 | 
			
		||||
	client: ExtendedClient
 | 
			
		||||
	interaction: ExtendedInteraction
 | 
			
		||||
	args: CommandInteractionOptionResolver
 | 
			
		||||
  client: ExtendedClient
 | 
			
		||||
  interaction: ExtendedInteraction
 | 
			
		||||
  args: CommandInteractionOptionResolver
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RunFunction = (options: RunOptions) => unknown
 | 
			
		||||
export type CommandType = {
 | 
			
		||||
	userPermissions?: PermissionResolvable[]
 | 
			
		||||
	run: RunFunction
 | 
			
		||||
  userPermissions?: PermissionResolvable[]
 | 
			
		||||
  run: RunFunction
 | 
			
		||||
} & ChatInputApplicationCommandData
 | 
			
		||||
 
 | 
			
		||||
@@ -2,100 +2,100 @@ import { add } from "date-fns"
 | 
			
		||||
import { CustomError, errorCodes } from "../interfaces"
 | 
			
		||||
 | 
			
		||||
export interface RepetitonInfo {
 | 
			
		||||
	startDate?: Date, // If defined will take precedence over repetitonAmount
 | 
			
		||||
	endDate?: Date,// If defined will take precedence over repetitonAmount
 | 
			
		||||
	totalAmount: number,
 | 
			
		||||
	alreadyOccured: number,
 | 
			
		||||
	schedule: Schedule
 | 
			
		||||
  startDate?: Date, // If defined will take precedence over repetitonAmount
 | 
			
		||||
  endDate?: Date,// If defined will take precedence over repetitonAmount
 | 
			
		||||
  totalAmount: number,
 | 
			
		||||
  alreadyOccured: number,
 | 
			
		||||
  schedule: Schedule
 | 
			
		||||
}
 | 
			
		||||
export const scheduleNames = ['daily', 'weekly', 'monthly', 'everyNWeeks', 'everyNDays', 'everyNMonths']
 | 
			
		||||
export type supportedSchedule = typeof scheduleNames[number]
 | 
			
		||||
export interface IScheduleType {
 | 
			
		||||
	name: supportedSchedule,
 | 
			
		||||
	multiplier: number,
 | 
			
		||||
	duration: Duration
 | 
			
		||||
  name: supportedSchedule,
 | 
			
		||||
  multiplier: number,
 | 
			
		||||
  duration: Duration
 | 
			
		||||
}
 | 
			
		||||
export const scheduleTypes: IScheduleType[] = [
 | 
			
		||||
	{
 | 
			
		||||
		name: 'daily',
 | 
			
		||||
		multiplier: 1,
 | 
			
		||||
		duration: {
 | 
			
		||||
			days: 1
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		name: 'weekly',
 | 
			
		||||
		multiplier: 1,
 | 
			
		||||
		duration: {
 | 
			
		||||
			weeks: 1
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
  {
 | 
			
		||||
    name: 'daily',
 | 
			
		||||
    multiplier: 1,
 | 
			
		||||
    duration: {
 | 
			
		||||
      days: 1
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'weekly',
 | 
			
		||||
    multiplier: 1,
 | 
			
		||||
    duration: {
 | 
			
		||||
      weeks: 1
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
]
 | 
			
		||||
export class Schedule {
 | 
			
		||||
	private scheduleName: string
 | 
			
		||||
	private multiplier = 1
 | 
			
		||||
	private duration: Duration
 | 
			
		||||
	private baseScheduleTypes = ['daily', 'weekly', 'monthly', 'yearly']
 | 
			
		||||
	private _scheduleString: string
 | 
			
		||||
	constructor(scheduleString: string) {
 | 
			
		||||
		this._scheduleString = scheduleString.toLowerCase()
 | 
			
		||||
		this.scheduleName = this._scheduleString
 | 
			
		||||
		if (this.baseScheduleTypes.includes(this._scheduleString)) {
 | 
			
		||||
			this.multiplier = 1
 | 
			
		||||
		}
 | 
			
		||||
		if (this._scheduleString.includes('every')) {
 | 
			
		||||
			this.scheduleName = this.getBaseScheduleNameFromVariableString()
 | 
			
		||||
			this.multiplier = this.getMultiplierFromVariableString()
 | 
			
		||||
		}
 | 
			
		||||
  private scheduleName: string
 | 
			
		||||
  private multiplier = 1
 | 
			
		||||
  private duration: Duration
 | 
			
		||||
  private baseScheduleTypes = ['daily', 'weekly', 'monthly', 'yearly']
 | 
			
		||||
  private _scheduleString: string
 | 
			
		||||
  constructor(scheduleString: string) {
 | 
			
		||||
    this._scheduleString = scheduleString.toLowerCase()
 | 
			
		||||
    this.scheduleName = this._scheduleString
 | 
			
		||||
    if (this.baseScheduleTypes.includes(this._scheduleString)) {
 | 
			
		||||
      this.multiplier = 1
 | 
			
		||||
    }
 | 
			
		||||
    if (this._scheduleString.includes('every')) {
 | 
			
		||||
      this.scheduleName = this.getBaseScheduleNameFromVariableString()
 | 
			
		||||
      this.multiplier = this.getMultiplierFromVariableString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
		switch (this.scheduleName) {
 | 
			
		||||
			case 'daily':
 | 
			
		||||
				this.duration = { days: 1 }
 | 
			
		||||
				break
 | 
			
		||||
			case 'weekly':
 | 
			
		||||
				this.duration = { weeks: 1 }
 | 
			
		||||
				break
 | 
			
		||||
			case 'monthly':
 | 
			
		||||
				this.duration = { months: 1 }
 | 
			
		||||
				break
 | 
			
		||||
			case 'yearly':
 | 
			
		||||
				this.duration = { years: 1 }
 | 
			
		||||
				break
 | 
			
		||||
			default:
 | 
			
		||||
				throw new CustomError('Schedule type not supported', errorCodes.schedule_not_supported)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	public getSanitizedScheduleString(): string {
 | 
			
		||||
		return this._scheduleString
 | 
			
		||||
	}
 | 
			
		||||
	private getBaseScheduleNameFromVariableString(): string {
 | 
			
		||||
		if (this._scheduleString.includes('week')) return 'weekly'
 | 
			
		||||
		if (this._scheduleString.includes('day')) return 'daily'
 | 
			
		||||
		if (this._scheduleString.includes('month')) return 'monthly'
 | 
			
		||||
		if (this._scheduleString.includes('year')) return 'yearly'
 | 
			
		||||
		return ''
 | 
			
		||||
	}
 | 
			
		||||
	public getMultiplierFromVariableString(): number {
 | 
			
		||||
		const matches = this._scheduleString.match(/\d+/)
 | 
			
		||||
		if (matches) {
 | 
			
		||||
			const multi = matches[0]
 | 
			
		||||
			if (multi)
 | 
			
		||||
				return parseInt(multi)
 | 
			
		||||
		}
 | 
			
		||||
		return 1
 | 
			
		||||
	}
 | 
			
		||||
	public calculateDuration(): Duration {
 | 
			
		||||
		const dur: Duration = {
 | 
			
		||||
			days: this.duration.days ? this.duration.days * this.multiplier : undefined,
 | 
			
		||||
			weeks: this.duration.weeks ? this.duration.weeks * this.multiplier : undefined,
 | 
			
		||||
			months: this.duration.months ? this.duration.months * this.multiplier : undefined,
 | 
			
		||||
			years: this.duration.years ? this.duration.years * this.multiplier : undefined,
 | 
			
		||||
		}
 | 
			
		||||
		return dur
 | 
			
		||||
	}
 | 
			
		||||
    switch (this.scheduleName) {
 | 
			
		||||
      case 'daily':
 | 
			
		||||
        this.duration = { days: 1 }
 | 
			
		||||
        break
 | 
			
		||||
      case 'weekly':
 | 
			
		||||
        this.duration = { weeks: 1 }
 | 
			
		||||
        break
 | 
			
		||||
      case 'monthly':
 | 
			
		||||
        this.duration = { months: 1 }
 | 
			
		||||
        break
 | 
			
		||||
      case 'yearly':
 | 
			
		||||
        this.duration = { years: 1 }
 | 
			
		||||
        break
 | 
			
		||||
      default:
 | 
			
		||||
        throw new CustomError('Schedule type not supported', errorCodes.schedule_not_supported)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  public getSanitizedScheduleString(): string {
 | 
			
		||||
    return this._scheduleString
 | 
			
		||||
  }
 | 
			
		||||
  private getBaseScheduleNameFromVariableString(): string {
 | 
			
		||||
    if (this._scheduleString.includes('week')) return 'weekly'
 | 
			
		||||
    if (this._scheduleString.includes('day')) return 'daily'
 | 
			
		||||
    if (this._scheduleString.includes('month')) return 'monthly'
 | 
			
		||||
    if (this._scheduleString.includes('year')) return 'yearly'
 | 
			
		||||
    return ''
 | 
			
		||||
  }
 | 
			
		||||
  public getMultiplierFromVariableString(): number {
 | 
			
		||||
    const matches = this._scheduleString.match(/\d+/)
 | 
			
		||||
    if (matches) {
 | 
			
		||||
      const multi = matches[0]
 | 
			
		||||
      if (multi)
 | 
			
		||||
        return parseInt(multi)
 | 
			
		||||
    }
 | 
			
		||||
    return 1
 | 
			
		||||
  }
 | 
			
		||||
  public calculateDuration(): Duration {
 | 
			
		||||
    const dur: Duration = {
 | 
			
		||||
      days: this.duration.days ? this.duration.days * this.multiplier : undefined,
 | 
			
		||||
      weeks: this.duration.weeks ? this.duration.weeks * this.multiplier : undefined,
 | 
			
		||||
      months: this.duration.months ? this.duration.months * this.multiplier : undefined,
 | 
			
		||||
      years: this.duration.years ? this.duration.years * this.multiplier : undefined,
 | 
			
		||||
    }
 | 
			
		||||
    return dur
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
	public getNewDate(oldDate: Date): Date {
 | 
			
		||||
		const newDate = add(oldDate, this.calculateDuration())
 | 
			
		||||
		return newDate
 | 
			
		||||
	}
 | 
			
		||||
  public getNewDate(oldDate: Date): Date {
 | 
			
		||||
    const newDate = add(oldDate, this.calculateDuration())
 | 
			
		||||
    return newDate
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user