Compare commits
3 Commits
0e67252976
...
e7b21fa658
Author | SHA1 | Date | |
---|---|---|---|
e7b21fa658 | |||
2d32f9b680 | |||
5503aa8713 |
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
root = true
|
||||||
|
[*]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
[*.ts]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
4
index.ts
4
index.ts
@ -5,8 +5,8 @@ import { JellyfinHandler } from "./server/jellyfin/handler"
|
|||||||
import { attachedImages } from "./server/assets/attachments"
|
import { attachedImages } from "./server/assets/attachments"
|
||||||
const requestId = 'startup'
|
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 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 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)
|
export const client = new ExtendedClient(jellyfinHandler)
|
||||||
|
|
||||||
|
@ -46,4 +46,4 @@
|
|||||||
"rimraf": "^5.0.0",
|
"rimraf": "^5.0.0",
|
||||||
"ts-jest": "^29.1.0"
|
"ts-jest": "^29.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,114 +8,114 @@ import { Command } from '../structures/command'
|
|||||||
import { RunOptions } from '../types/commandTypes'
|
import { RunOptions } from '../types/commandTypes'
|
||||||
|
|
||||||
export default new Command({
|
export default new Command({
|
||||||
name: 'announce',
|
name: 'announce',
|
||||||
description: 'Neues announcement im announcement Channel an alle senden.',
|
description: 'Neues announcement im announcement Channel an alle senden.',
|
||||||
options: [{
|
options: [{
|
||||||
name: "typ",
|
name: "typ",
|
||||||
type: ApplicationCommandOptionType.String,
|
type: ApplicationCommandOptionType.String,
|
||||||
description:"Was für ein announcement?",
|
description: "Was für ein announcement?",
|
||||||
choices: [{name: "initial", value:"initial"},{name: "votepls", value:"votepls"},{name: "cancel", value:"cancel"}],
|
choices: [{ name: "initial", value: "initial" }, { name: "votepls", value: "votepls" }, { name: "cancel", value: "cancel" }],
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
run: async (interaction: RunOptions) => {
|
run: async (interaction: RunOptions) => {
|
||||||
const command = interaction.interaction
|
const command = interaction.interaction
|
||||||
const requestId = uuid()
|
const requestId = uuid()
|
||||||
if(!command.guildId) {
|
if (!command.guildId) {
|
||||||
logger.error("COMMAND DOES NOT HAVE A GUILD ID; CANCELLING!!!", {requestId})
|
logger.error("COMMAND DOES NOT HAVE A GUILD ID; CANCELLING!!!", { requestId })
|
||||||
return
|
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`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 {
|
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> {
|
async function sendInitialAnnouncement(guildId: string, requestId: string): Promise<void> {
|
||||||
logger.info("Sending initial announcement")
|
logger.info("Sending initial announcement")
|
||||||
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
|
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
|
||||||
if(!announcementChannel) {
|
if (!announcementChannel) {
|
||||||
logger.error("Could not find announcement channel. Aborting", { guildId, requestId })
|
logger.error("Could not find announcement channel. Aborting", { guildId, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentPinnedAnnouncementMessages = (await announcementChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
|
const currentPinnedAnnouncementMessages = (await announcementChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
|
||||||
currentPinnedAnnouncementMessages.forEach(async (message) => await message.unpin())
|
currentPinnedAnnouncementMessages.forEach(async (message) => await message.unpin())
|
||||||
currentPinnedAnnouncementMessages.forEach(message => message.delete())
|
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.
|
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.`
|
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 = {
|
const options: MessageCreateOptions = {
|
||||||
allowedMentions: { parse: ['everyone'] },
|
allowedMentions: { parse: ['everyone'] },
|
||||||
content: body
|
content: body
|
||||||
}
|
}
|
||||||
const message: Message<true> = await announcementChannel.send(options)
|
const message: Message<true> = await announcementChannel.send(options)
|
||||||
await message.react("🎫")
|
await message.react("🎫")
|
||||||
await message.pin()
|
await message.pin()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function manageAnnouncementRoles(guild: Guild, reaction: MessageReaction, requestId: string) {
|
export async function manageAnnouncementRoles(guild: Guild, reaction: MessageReaction, requestId: string) {
|
||||||
const guildId = guild.id
|
const guildId = guild.id
|
||||||
logger.info("Managing roles", { guildId, requestId })
|
logger.info("Managing roles", { guildId, requestId })
|
||||||
|
|
||||||
const announcementRole: Role | undefined = (await guild.roles.fetch()).find(role => role.id === config.bot.announcement_role)
|
const announcementRole: Role | undefined = (await guild.roles.fetch()).find(role => role.id === config.bot.announcement_role)
|
||||||
if (!announcementRole) {
|
if (!announcementRole) {
|
||||||
logger.error(`Could not find announcement role! Aborting! Was looking for role with id: ${config.bot.announcement_role}`, { guildId, requestId })
|
logger.error(`Could not find announcement role! Aborting! Was looking for role with id: ${config.bot.announcement_role}`, { guildId, requestId })
|
||||||
return
|
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
|
const usersWhoHaveRole: GuildMember[] = allUsers
|
||||||
.filter(member=> member.roles.cache
|
.filter(member => member.roles.cache
|
||||||
.find(role => role.id === config.bot.announcement_role) !== undefined)
|
.find(role => role.id === config.bot.announcement_role) !== undefined)
|
||||||
.map(member => member)
|
.map(member => member)
|
||||||
|
|
||||||
const usersWhoNeedRoleRevoked: GuildMember[] = usersWhoHaveRole
|
const usersWhoNeedRoleRevoked: GuildMember[] = usersWhoHaveRole
|
||||||
.filter(userWhoHas => !usersWhoWantRole.map(wanter => wanter.id).includes(userWhoHas.id))
|
.filter(userWhoHas => !usersWhoWantRole.map(wanter => wanter.id).includes(userWhoHas.id))
|
||||||
|
|
||||||
const usersWhoDontHaveRole: GuildMember[] = allUsers
|
const usersWhoDontHaveRole: GuildMember[] = allUsers
|
||||||
.filter(member => member.roles.cache
|
.filter(member => member.roles.cache
|
||||||
.find(role=> role.id === config.bot.announcement_role) === undefined)
|
.find(role => role.id === config.bot.announcement_role) === undefined)
|
||||||
.map(member => member)
|
.map(member => member)
|
||||||
|
|
||||||
const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole
|
const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole
|
||||||
.filter(userWhoNeeds => usersWhoWantRole.map(wanter => wanter.id).includes(userWhoNeeds.id))
|
.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 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 added: ${JSON.stringify(usersWhoNeedRole)}`, { guildId, requestId })
|
||||||
|
|
||||||
usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole))
|
usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole))
|
||||||
usersWhoNeedRole.forEach(user => user.roles.add(announcementRole))
|
usersWhoNeedRole.forEach(user => user.roles.add(announcementRole))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,175 +10,175 @@ import { Command } from '../structures/command'
|
|||||||
import { RunOptions } from '../types/commandTypes'
|
import { RunOptions } from '../types/commandTypes'
|
||||||
|
|
||||||
export default new Command({
|
export default new Command({
|
||||||
name: 'closepoll',
|
name: 'closepoll',
|
||||||
description: 'Aktuelle Umfrage für nächste Watchparty beenden und Gewinner in Event eintragen.',
|
description: 'Aktuelle Umfrage für nächste Watchparty beenden und Gewinner in Event eintragen.',
|
||||||
options: [],
|
options: [],
|
||||||
run: async (interaction: RunOptions) => {
|
run: async (interaction: RunOptions) => {
|
||||||
const command = interaction.interaction
|
const command = interaction.interaction
|
||||||
const requestId = uuid()
|
const requestId = uuid()
|
||||||
if (!command.guild) {
|
if (!command.guild) {
|
||||||
logger.error("No guild found in interaction. Cancelling closing request", { requestId })
|
logger.error("No guild found in interaction. Cancelling closing request", { requestId })
|
||||||
command.followUp("Es gab leider ein Problem. Ich konnte deine Anfrage nicht bearbeiten :(")
|
command.followUp("Es gab leider ein Problem. Ich konnte deine Anfrage nicht bearbeiten :(")
|
||||||
return
|
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)
|
|
||||||
}
|
}
|
||||||
|
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) {
|
export async function closePoll(guild: Guild, requestId: string) {
|
||||||
const guildId = guild.id
|
const guildId = guild.id
|
||||||
logger.info("stopping poll", { guildId, requestId })
|
logger.info("stopping poll", { guildId, requestId })
|
||||||
|
|
||||||
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
|
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
|
||||||
if (!announcementChannel) {
|
if (!announcementChannel) {
|
||||||
logger.error("Could not find the textchannel. Unable to close poll.", { guildId, requestId })
|
logger.error("Could not find the textchannel. Unable to close poll.", { guildId, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages: Message<true>[] = (await announcementChannel.messages.fetch()) //todo: fetch only pinned messages
|
const messages: Message<true>[] = (await announcementChannel.messages.fetch()) //todo: fetch only pinned messages
|
||||||
.map((value) => value)
|
.map((value) => value)
|
||||||
.filter(message => !message.cleanContent.includes("[Abstimmung beendet]") && message.cleanContent.includes("[Abstimmung]"))
|
.filter(message => !message.cleanContent.includes("[Abstimmung beendet]") && message.cleanContent.includes("[Abstimmung]"))
|
||||||
.sort((a, b) => b.createdTimestamp - a.createdTimestamp)
|
.sort((a, b) => b.createdTimestamp - a.createdTimestamp)
|
||||||
|
|
||||||
if (!messages || messages.length <= 0) {
|
if (!messages || messages.length <= 0) {
|
||||||
logger.info("Could not find any vote messages. Cancelling pollClose", { guildId, requestId })
|
logger.info("Could not find any vote messages. Cancelling pollClose", { guildId, requestId })
|
||||||
return
|
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))
|
const votes = await (await getVotesByEmote(lastMessage, guildId, requestId))
|
||||||
.sort((a, b) => b.count - a.count)
|
.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")
|
logger.info("Deleting vote message")
|
||||||
await lastMessage.delete()
|
await lastMessage.delete()
|
||||||
const event = await getEvent(guild, guild.id, requestId)
|
const event = await getEvent(guild, guild.id, requestId)
|
||||||
if (event) {
|
if (event) {
|
||||||
updateEvent(event, votes, guild, guildId, requestId)
|
updateEvent(event, votes, guild, guildId, requestId)
|
||||||
sendVoteClosedMessage(event, votes[0].movie, 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) {
|
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 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 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 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 = {
|
const options: MessageCreateOptions = {
|
||||||
content: body,
|
content: body,
|
||||||
allowedMentions: { parse: ["roles"] }
|
allowedMentions: { parse: ["roles"] }
|
||||||
}
|
}
|
||||||
const announcementChannel = client.getAnnouncementChannelForGuild(guildId)
|
const announcementChannel = client.getAnnouncementChannelForGuild(guildId)
|
||||||
logger.info("Sending vote closed message.", { guildId, requestId })
|
logger.info("Sending vote closed message.", { guildId, requestId })
|
||||||
if (!announcementChannel) {
|
if (!announcementChannel) {
|
||||||
logger.error("Could not find announcement channel. Please fix!", { guildId, requestId })
|
logger.error("Could not find announcement channel. Please fix!", { guildId, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
announcementChannel.send(options)
|
announcementChannel.send(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateEvent(voteEvent: GuildScheduledEvent, votes: Vote[], guild: Guild, guildId: string, requestId: string) {
|
async function updateEvent(voteEvent: GuildScheduledEvent, votes: Vote[], guild: Guild, guildId: string, requestId: string) {
|
||||||
logger.info(`Updating event with movie ${votes[0].movie}.`, { guildId, requestId })
|
logger.info(`Updating event with movie ${votes[0].movie}.`, { guildId, requestId })
|
||||||
const options: GuildScheduledEventEditOptions<GuildScheduledEventStatus.Scheduled, GuildScheduledEventSetStatusArg<GuildScheduledEventStatus.Scheduled>> = {
|
const options: GuildScheduledEventEditOptions<GuildScheduledEventStatus.Scheduled, GuildScheduledEventSetStatusArg<GuildScheduledEventStatus.Scheduled>> = {
|
||||||
name: votes[0].movie,
|
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`
|
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.debug(`Updating event: ${JSON.stringify(voteEvent, null, 2)}`, { guildId, requestId })
|
||||||
logger.info("Updating event.", { guildId, requestId })
|
logger.info("Updating event.", { guildId, requestId })
|
||||||
voteEvent.edit(options)
|
voteEvent.edit(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getEvent(guild: Guild, guildId: string, requestId: string): Promise<GuildScheduledEvent | null> {
|
async function getEvent(guild: Guild, guildId: string, requestId: string): Promise<GuildScheduledEvent | null> {
|
||||||
const voteEvents = (await guild.scheduledEvents.fetch())
|
const voteEvents = (await guild.scheduledEvents.fetch())
|
||||||
.map((value) => value)
|
.map((value) => value)
|
||||||
.filter(event => event.name.toLowerCase().includes("voting offen"))
|
.filter(event => event.name.toLowerCase().includes("voting offen"))
|
||||||
logger.debug(`Found events: ${JSON.stringify(voteEvents, null, 2)}`, { guildId, requestId })
|
logger.debug(`Found events: ${JSON.stringify(voteEvents, null, 2)}`, { guildId, requestId })
|
||||||
|
|
||||||
if (!voteEvents || voteEvents.length <= 0) {
|
if (!voteEvents || voteEvents.length <= 0) {
|
||||||
logger.error("Could not find vote event. Cancelling update!", { guildId, requestId })
|
logger.error("Could not find vote event. Cancelling update!", { guildId, requestId })
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return voteEvents[0]
|
return voteEvents[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Vote = {
|
type Vote = {
|
||||||
emote: string, //todo habs nicht hinbekommen hier Emotes zu nutzen
|
emote: string, //todo habs nicht hinbekommen hier Emotes zu nutzen
|
||||||
count: number,
|
count: number,
|
||||||
movie: string
|
movie: string
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getVotesByEmote(message: Message, guildId: string, requestId: string): Promise<Vote[]> {
|
async function getVotesByEmote(message: Message, guildId: string, requestId: string): Promise<Vote[]> {
|
||||||
const votes: Vote[] = []
|
const votes: Vote[] = []
|
||||||
logger.debug(`Number of items in emotes: ${Object.values(Emotes).length}`, { guildId, requestId })
|
logger.debug(`Number of items in emotes: ${Object.values(Emotes).length}`, { guildId, requestId })
|
||||||
for (let i = 0; i < Object.keys(Emotes).length / 2; i++) {
|
for (let i = 0; i < Object.keys(Emotes).length / 2; i++) {
|
||||||
const emote = Emotes[i]
|
const emote = Emotes[i]
|
||||||
logger.debug(`Getting reaction for emote ${emote}`, { guildId, requestId })
|
logger.debug(`Getting reaction for emote ${emote}`, { guildId, requestId })
|
||||||
const reaction = await message.reactions.resolve(emote)
|
const reaction = await message.reactions.resolve(emote)
|
||||||
logger.debug(`Reaction for emote ${emote}: ${JSON.stringify(reaction, null, 2)}`, { guildId, requestId })
|
logger.debug(`Reaction for emote ${emote}: ${JSON.stringify(reaction, null, 2)}`, { guildId, requestId })
|
||||||
if (reaction) {
|
if (reaction) {
|
||||||
const vote: Vote = { emote: emote, count: reaction.count, movie: extractMovieFromMessageByEmote(message, emote) }
|
const vote: Vote = { emote: emote, count: reaction.count, movie: extractMovieFromMessageByEmote(message, emote) }
|
||||||
votes.push(vote)
|
votes.push(vote)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return votes
|
}
|
||||||
|
return votes
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractMovieFromMessageByEmote(message: Message, emote: string): string {
|
function extractMovieFromMessageByEmote(message: Message, emote: string): string {
|
||||||
const lines = message.cleanContent.split("\n")
|
const lines = message.cleanContent.split("\n")
|
||||||
const emoteLines = lines.filter(line => line.includes(emote))
|
const emoteLines = lines.filter(line => line.includes(emote))
|
||||||
|
|
||||||
if (!emoteLines) {
|
if (!emoteLines) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
const movie = emoteLines[0].substring(emoteLines[0].indexOf(emote) + emote.length + 2) // plus colon and space
|
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> {
|
export async function checkForPollsToClose(guild: Guild): Promise<void> {
|
||||||
const requestId = uuid()
|
const requestId = uuid()
|
||||||
logger.info(`Automatic check for poll closing.`, { guildId: guild.id, requestId })
|
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)
|
const events = (await guild.scheduledEvents.fetch()).filter(event => event.name.toLocaleLowerCase().includes("voting offen")).map(event => event)
|
||||||
if (events.length > 1) {
|
if (events.length > 1) {
|
||||||
logger.error("Handling more than one Event is not implemented yet. Found more than one poll to close")
|
logger.error("Handling more than one Event is not implemented yet. Found more than one poll to close")
|
||||||
return
|
return
|
||||||
} else if (events.length == 0) {
|
} else if (events.length == 0) {
|
||||||
logger.info("Could not find any events. Cancelling", { guildId: guild.id, requestId })
|
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()
|
const updatedEvent = events[0] //add two hours because of different timezones in discord api and Date.now()
|
||||||
if (!updatedEvent.scheduledStartTimestamp) {
|
if (!updatedEvent.scheduledStartTimestamp) {
|
||||||
logger.error("Event does not have a scheduled start time. Cancelling", { guildId: guild.id, requestId })
|
logger.error("Event does not have a scheduled start time. Cancelling", { guildId: guild.id, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const createDate: Date = toDate(updatedEvent.createdTimestamp)
|
const createDate: Date = toDate(updatedEvent.createdTimestamp)
|
||||||
const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp)
|
const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp)
|
||||||
const difference: number = differenceInDays(createDate, eventDate)
|
const difference: number = differenceInDays(createDate, eventDate)
|
||||||
|
|
||||||
if (difference <= 2) {
|
if (difference <= 2) {
|
||||||
logger.info("Less than two days between event create and event start. Not closing poll.", { guildId: guild.id, requestId })
|
logger.info("Less than two days between event create and event start. Not closing poll.", { guildId: guild.id, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const closePollDate: Date = addDays(eventDate, -2)
|
const closePollDate: Date = addDays(eventDate, -2)
|
||||||
|
|
||||||
if (isAfter(Date.now(), closePollDate)) {
|
if (isAfter(Date.now(), closePollDate)) {
|
||||||
logger.info("Less than two days until event. Closing poll", { guildId: guild.id, requestId })
|
logger.info("Less than two days until event. Closing poll", { guildId: guild.id, requestId })
|
||||||
closePoll(guild, requestId)
|
closePoll(guild, requestId)
|
||||||
} else {
|
} else {
|
||||||
logger.info(`ScheduledStart: ${closePollDate}. Now: ${toDate(Date.now())}`, { guildId: guild.id, requestId })
|
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 { Command } from '../structures/command'
|
||||||
import { RunOptions } from '../types/commandTypes'
|
import { RunOptions } from '../types/commandTypes'
|
||||||
export default new Command({
|
export default new Command({
|
||||||
name: 'echo',
|
name: 'echo',
|
||||||
description: 'Echoes a text',
|
description: 'Echoes a text',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'echo',
|
name: 'echo',
|
||||||
description: 'The text to echo',
|
description: 'The text to echo',
|
||||||
type: ApplicationCommandOptionType.String,
|
type: ApplicationCommandOptionType.String,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
run: async (interaction: RunOptions) => {
|
run: async (interaction: RunOptions) => {
|
||||||
console.log('echo called')
|
console.log('echo called')
|
||||||
interaction.interaction.reply(interaction.toString())
|
interaction.interaction.reply(interaction.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -4,13 +4,13 @@ import { Command } from '../structures/command'
|
|||||||
import { RunOptions } from '../types/commandTypes'
|
import { RunOptions } from '../types/commandTypes'
|
||||||
|
|
||||||
export default new Command({
|
export default new Command({
|
||||||
name: 'passwort_reset',
|
name: 'passwort_reset',
|
||||||
description: 'Ich vergebe dir ein neues Passwort und schicke es dir per DM zu. Kostet auch nix! Versprochen! 😉',
|
description: 'Ich vergebe dir ein neues Passwort und schicke es dir per DM zu. Kostet auch nix! Versprochen! 😉',
|
||||||
options: [],
|
options: [],
|
||||||
run: async (interaction: RunOptions) => {
|
run: async (interaction: RunOptions) => {
|
||||||
console.log('PasswortReset called')
|
console.log('PasswortReset called')
|
||||||
interaction.interaction.followUp('Yo, ich schick dir eins!')
|
interaction.interaction.followUp('Yo, ich schick dir eins!')
|
||||||
console.log(JSON.stringify(interaction.interaction.member, null, 2))
|
console.log(JSON.stringify(interaction.interaction.member, null, 2))
|
||||||
jellyfinHandler.resetUserPasswort(interaction.interaction.member, uuid())
|
jellyfinHandler.resetUserPasswort(interaction.interaction.member, uuid())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -10,39 +10,39 @@ import { logger } from "../logger";
|
|||||||
export const name = 'guildScheduledEventCreate'
|
export const name = 'guildScheduledEventCreate'
|
||||||
|
|
||||||
export async function execute(event: GuildScheduledEvent) {
|
export async function execute(event: GuildScheduledEvent) {
|
||||||
const guildId = event.guildId
|
const guildId = event.guildId
|
||||||
const requestId = uuid()
|
const requestId = uuid()
|
||||||
try {
|
try {
|
||||||
if (!event.description) {
|
if (!event.description) {
|
||||||
logger.debug("Got GuildScheduledEventCreate event. But has no description. Aborting.")
|
logger.debug("Got GuildScheduledEventCreate event. But has no description. Aborting.")
|
||||||
return
|
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 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 let task: ScheduledTask | undefined
|
||||||
|
|
||||||
export async function execute(event: GuildScheduledEvent) {
|
export async function execute(event: GuildScheduledEvent) {
|
||||||
const requestId = uuid()
|
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)
|
|
||||||
|
|
||||||
logger.info(`Got ${movies.length} random movies. Creating voting`, { guildId: event.guildId, requestId })
|
if (event.name.toLowerCase().includes("!nextwp")) {
|
||||||
logger.debug(`Movies: ${JSON.stringify(movies)}`, { guildId: event.guildId, requestId })
|
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)
|
logger.info(`Got ${movies.length} random movies. Creating voting`, { guildId: event.guildId, requestId })
|
||||||
if(!announcementChannel) {
|
logger.debug(`Movies: ${JSON.stringify(movies)}`, { guildId: event.guildId, requestId })
|
||||||
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) {
|
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(event.guildId)
|
||||||
logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", {guildId: event.guildId, requestId})
|
if (!announcementChannel) {
|
||||||
return
|
logger.error("Could not find announcement channel. Aborting", { 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
|
|
||||||
}
|
}
|
||||||
|
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 const name = 'guildScheduledEventUpdate'
|
||||||
|
|
||||||
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
|
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
|
||||||
const requestId = uuid()
|
const requestId = uuid()
|
||||||
try {
|
try {
|
||||||
if (!newEvent.guild) {
|
if (!newEvent.guild) {
|
||||||
logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
|
logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
|
||||||
return
|
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 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>[] {
|
function filterAnnouncementsByPendingWPs(messages: Collection<string, Message<true>>, events: Collection<string, GuildScheduledEvent<GuildScheduledEventStatus>>): Message<true>[] {
|
||||||
const filteredMessages: Message<true>[] = []
|
const filteredMessages: Message<true>[] = []
|
||||||
for (const message of messages.values()) {
|
for (const message of messages.values()) {
|
||||||
let foundEventForMessage = false
|
let foundEventForMessage = false
|
||||||
for (const event of events.values()) {
|
for (const event of events.values()) {
|
||||||
if (message.cleanContent.includes(event.id)) { //announcement always has eventid because of eventbox
|
if (message.cleanContent.includes(event.id)) { //announcement always has eventid because of eventbox
|
||||||
foundEventForMessage = true
|
foundEventForMessage = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if(!foundEventForMessage){
|
|
||||||
filteredMessages.push(message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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) {
|
export async function execute(oldState: VoiceState, newState: VoiceState) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info(JSON.stringify(newState, null, 2))
|
logger.info(JSON.stringify(newState, null, 2))
|
||||||
//ignore events like mute/unmute
|
//ignore events like mute/unmute
|
||||||
if(newState.channel?.id === oldState.channel?.id) {
|
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)")
|
logger.info("Not handling VoiceState event because channelid of old and new was the same (i.e. mute/unmute event)")
|
||||||
return
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 const name = 'guildScheduledEventUpdate'
|
||||||
|
|
||||||
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
|
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
|
||||||
try {
|
try {
|
||||||
const requestId = uuid()
|
const requestId = uuid()
|
||||||
// logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`, { guildId: newEvent.guildId, requestId })
|
// logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`, { guildId: newEvent.guildId, requestId })
|
||||||
if (!newEvent.guild) {
|
if (!newEvent.guild) {
|
||||||
logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
|
logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
|
||||||
return
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
async function createJFUsers(members: GuildMember[], movieName: string, requestId?: string) {
|
||||||
logger.info(`Creating users for: \n ${JSON.stringify(members, null, 2)}`)
|
logger.info(`Creating users for: \n ${JSON.stringify(members, null, 2)}`)
|
||||||
members.forEach(member => {
|
members.forEach(member => {
|
||||||
member.createDM().then(channel => channel.send(`Hey! Du hast dich für die Watchparty von ${movieName} angemeldet! Es geht gleich los!`))
|
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)
|
jellyfinHandler.upsertUser(member, "TEMPORARY", requestId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteJFUsers(guildId: string, requestId?: string) {
|
async function deleteJFUsers(guildId: string, requestId?: string) {
|
||||||
logger.info(`Watchparty ended, deleting tmp users`, { guildId, requestId })
|
logger.info(`Watchparty ended, deleting tmp users`, { guildId, requestId })
|
||||||
jellyfinHandler.purge(guildId, requestId)
|
jellyfinHandler.purge(guildId, requestId)
|
||||||
}
|
}
|
@ -2,5 +2,5 @@ import { Message } from "discord.js"
|
|||||||
|
|
||||||
export const name = 'messageCreate'
|
export const name = 'messageCreate'
|
||||||
export function execute(message: Message) {
|
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 { format, isToday, toDate } from "date-fns";
|
||||||
import {utcToZonedTime} from "date-fns-tz"
|
import { utcToZonedTime } from "date-fns-tz"
|
||||||
import { GuildScheduledEvent } from "discord.js";
|
import { GuildScheduledEvent } from "discord.js";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import de from "date-fns/locale/de";
|
import de from "date-fns/locale/de";
|
||||||
|
|
||||||
export function createDateStringFromEvent(event: GuildScheduledEvent, requestId: string, guildId?: string): string {
|
export function createDateStringFromEvent(event: GuildScheduledEvent, requestId: string, guildId?: string): string {
|
||||||
if(!event.scheduledStartAt) {
|
if (!event.scheduledStartAt) {
|
||||||
logger.error("Event has no start. Cannot create dateString.", {guildId, requestId})
|
logger.error("Event has no start. Cannot create dateString.", { guildId, requestId })
|
||||||
return `"habe keinen Startzeitpunkt ermitteln können"`
|
return `"habe keinen Startzeitpunkt ermitteln können"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeZone = 'Europe/Berlin'
|
const timeZone = 'Europe/Berlin'
|
||||||
const zonedDateTime = utcToZonedTime(event.scheduledStartAt, timeZone)
|
const zonedDateTime = utcToZonedTime(event.scheduledStartAt, timeZone)
|
||||||
const time = format(zonedDateTime, "HH:mm", {locale: de})
|
const time = format(zonedDateTime, "HH:mm", { locale: de })
|
||||||
|
|
||||||
if(isToday(zonedDateTime)) {
|
|
||||||
return `heute um ${time}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const date = format(zonedDateTime, "eeee dd.MM", {locale: de})
|
if (isToday(zonedDateTime)) {
|
||||||
return `am ${date} um ${time}`
|
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 })
|
logger.debug(JSON.stringify(req), { requestId, guildId })
|
||||||
const createResult = await this.userApi.createUserByName(req)
|
const createResult = await this.userApi.createUserByName(req)
|
||||||
if (createResult) {
|
if (createResult) {
|
||||||
if(createResult.policy) {
|
if (createResult.policy) {
|
||||||
this.setUserPermissions(createResult, requestId, guildId)
|
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}"!`)
|
(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) {
|
public async setUserPermissions(user: UserDto, requestId: string, guildId?: string) {
|
||||||
if(!user.policy || !user.id) {
|
if (!user.policy || !user.id) {
|
||||||
logger.error(`Cannot update user policy. User ${user.name} has no policy to modify`, {guildId, requestId})
|
logger.error(`Cannot update user policy. User ${user.name} has no policy to modify`, { guildId, requestId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.policy.enableVideoPlaybackTranscoding = false
|
user.policy.enableVideoPlaybackTranscoding = false
|
||||||
@ -273,7 +273,7 @@ export class JellyfinHandler {
|
|||||||
let movieCount = 0
|
let movieCount = 0
|
||||||
let movieNames: string[]
|
let movieNames: string[]
|
||||||
do {
|
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
|
movieCount = movieNames.length
|
||||||
} while (movieCount < count)
|
} while (movieCount < count)
|
||||||
return movieNames
|
return movieNames
|
||||||
|
@ -16,72 +16,72 @@
|
|||||||
export const BASE_PATH = "http://localhost".replace(/\/+$/, "");
|
export const BASE_PATH = "http://localhost".replace(/\/+$/, "");
|
||||||
|
|
||||||
export interface ConfigurationParameters {
|
export interface ConfigurationParameters {
|
||||||
basePath?: string; // override base path
|
basePath?: string; // override base path
|
||||||
fetchApi?: FetchAPI; // override for fetch implementation
|
fetchApi?: FetchAPI; // override for fetch implementation
|
||||||
middleware?: Middleware[]; // middleware to apply before/after fetch requests
|
middleware?: Middleware[]; // middleware to apply before/after fetch requests
|
||||||
queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings
|
queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings
|
||||||
username?: string; // parameter for basic security
|
username?: string; // parameter for basic security
|
||||||
password?: string; // parameter for basic security
|
password?: string; // parameter for basic security
|
||||||
apiKey?: string | ((name: string) => string); // parameter for apiKey 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
|
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
|
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
|
credentials?: RequestCredentials; //value for the credentials param we want to use on each request
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Configuration {
|
export class Configuration {
|
||||||
constructor(private configuration: ConfigurationParameters = {}) {}
|
constructor(private configuration: ConfigurationParameters = {}) { }
|
||||||
|
|
||||||
set config(configuration: Configuration) {
|
set config(configuration: Configuration) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
get basePath(): string {
|
get basePath(): string {
|
||||||
return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH;
|
return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fetchApi(): FetchAPI | undefined {
|
get fetchApi(): FetchAPI | undefined {
|
||||||
return this.configuration.fetchApi;
|
return this.configuration.fetchApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
get middleware(): Middleware[] {
|
get middleware(): Middleware[] {
|
||||||
return this.configuration.middleware || [];
|
return this.configuration.middleware || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get queryParamsStringify(): (params: HTTPQuery) => string {
|
get queryParamsStringify(): (params: HTTPQuery) => string {
|
||||||
return this.configuration.queryParamsStringify || querystring;
|
return this.configuration.queryParamsStringify || querystring;
|
||||||
}
|
}
|
||||||
|
|
||||||
get username(): string | undefined {
|
get username(): string | undefined {
|
||||||
return this.configuration.username;
|
return this.configuration.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
get password(): string | undefined {
|
get password(): string | undefined {
|
||||||
return this.configuration.password;
|
return this.configuration.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
get apiKey(): ((name: string) => string) | undefined {
|
get apiKey(): ((name: string) => string) | undefined {
|
||||||
const apiKey = this.configuration.apiKey;
|
const apiKey = this.configuration.apiKey;
|
||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
return typeof apiKey === 'function' ? apiKey : () => apiKey;
|
return typeof apiKey === 'function' ? apiKey : () => apiKey;
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
get accessToken(): ((name?: string, scopes?: string[]) => string | Promise<string>) | undefined {
|
get accessToken(): ((name?: string, scopes?: string[]) => string | Promise<string>) | undefined {
|
||||||
const accessToken = this.configuration.accessToken;
|
const accessToken = this.configuration.accessToken;
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
return typeof accessToken === 'function' ? accessToken : async () => accessToken;
|
return typeof accessToken === 'function' ? accessToken : async () => accessToken;
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
get headers(): HTTPHeaders | undefined {
|
get headers(): HTTPHeaders | undefined {
|
||||||
return this.configuration.headers;
|
return this.configuration.headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
get credentials(): RequestCredentials | undefined {
|
get credentials(): RequestCredentials | undefined {
|
||||||
return this.configuration.credentials;
|
return this.configuration.credentials;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DefaultConfig = new Configuration();
|
export const DefaultConfig = new Configuration();
|
||||||
@ -91,192 +91,192 @@ export const DefaultConfig = new Configuration();
|
|||||||
*/
|
*/
|
||||||
export class BaseAPI {
|
export class BaseAPI {
|
||||||
|
|
||||||
private static readonly jsonRegex = new RegExp('^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i');
|
private static readonly jsonRegex = new RegExp('^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i');
|
||||||
private middleware: Middleware[];
|
private middleware: Middleware[];
|
||||||
|
|
||||||
constructor(protected configuration = DefaultConfig) {
|
constructor(protected configuration = DefaultConfig) {
|
||||||
this.middleware = configuration.middleware;
|
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 headers = Object.assign({}, this.configuration.headers, context.headers);
|
||||||
const next = this.clone<T>();
|
Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {});
|
||||||
next.middleware = next.middleware.concat(...middlewares);
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
withPreMiddleware<T extends BaseAPI>(this: T, ...preMiddlewares: Array<Middleware['pre']>) {
|
const initOverrideFn =
|
||||||
const middlewares = preMiddlewares.map((pre) => ({ pre }));
|
typeof initOverrides === "function"
|
||||||
return this.withMiddleware<T>(...middlewares);
|
? initOverrides
|
||||||
}
|
: async () => initOverrides;
|
||||||
|
|
||||||
withPostMiddleware<T extends BaseAPI>(this: T, ...postMiddlewares: Array<Middleware['post']>) {
|
const initParams = {
|
||||||
const middlewares = postMiddlewares.map((post) => ({ post }));
|
method: context.method,
|
||||||
return this.withMiddleware<T>(...middlewares);
|
headers,
|
||||||
}
|
body: context.body,
|
||||||
|
credentials: this.configuration.credentials,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
const overriddenInit: RequestInit = {
|
||||||
* Check if the given MIME is a JSON MIME.
|
...initParams,
|
||||||
* JSON MIME examples:
|
...(await initOverrideFn({
|
||||||
* application/json
|
init: initParams,
|
||||||
* application/json; charset=UTF8
|
context,
|
||||||
* APPLICATION/JSON
|
}))
|
||||||
* application/vnd.company+json
|
};
|
||||||
* @param mime - MIME (Multipurpose Internet Mail Extensions)
|
|
||||||
* @return True if the given MIME is JSON, false otherwise.
|
const init: RequestInit = {
|
||||||
*/
|
...overriddenInit,
|
||||||
protected isJsonMime(mime: string | null | undefined): boolean {
|
body:
|
||||||
if (!mime) {
|
isFormData(overriddenInit.body) ||
|
||||||
return false;
|
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);
|
}
|
||||||
}
|
if (response === undefined) {
|
||||||
|
if (e instanceof Error) {
|
||||||
protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise<Response> {
|
throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response');
|
||||||
const { url, init } = await this.createFetchParams(context, initOverrides);
|
} else {
|
||||||
const response = await this.fetchApi(url, init);
|
throw e;
|
||||||
if (response && (response.status >= 200 && response.status < 300)) {
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
throw new ResponseError(response, 'Response returned an error code');
|
}
|
||||||
}
|
}
|
||||||
|
for (const middleware of this.middleware) {
|
||||||
private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) {
|
if (middleware.post) {
|
||||||
let url = this.configuration.basePath + context.path;
|
response = await middleware.post({
|
||||||
if (context.query !== undefined && Object.keys(context.query).length !== 0) {
|
fetch: this.fetchApi,
|
||||||
// only add the querystring to the URL if there are query parameters.
|
url: fetchParams.url,
|
||||||
// this is done to avoid urls ending with a "?" character which buggy webservers
|
init: fetchParams.init,
|
||||||
// do not handle correctly sometimes.
|
response: response.clone(),
|
||||||
url += '?' + this.configuration.queryParamsStringify(context.query);
|
}) || response;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 };
|
|
||||||
}
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
private fetchApi = async (url: string, init: RequestInit) => {
|
/**
|
||||||
let fetchParams = { url, init };
|
* Create a shallow clone of `this` by constructing a new instance
|
||||||
for (const middleware of this.middleware) {
|
* and then shallow cloning data members.
|
||||||
if (middleware.pre) {
|
*/
|
||||||
fetchParams = await middleware.pre({
|
private clone<T extends BaseAPI>(this: T): T {
|
||||||
fetch: this.fetchApi,
|
const constructor = this.constructor as any;
|
||||||
...fetchParams,
|
const next = new constructor(this.configuration);
|
||||||
}) || fetchParams;
|
next.middleware = this.middleware.slice();
|
||||||
}
|
return next;
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function isBlob(value: any): value is Blob {
|
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 {
|
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 {
|
export class ResponseError extends Error {
|
||||||
override name: "ResponseError" = "ResponseError";
|
override name: "ResponseError" = "ResponseError";
|
||||||
constructor(public response: Response, msg?: string) {
|
constructor(public response: Response, msg?: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FetchError extends Error {
|
export class FetchError extends Error {
|
||||||
override name: "FetchError" = "FetchError";
|
override name: "FetchError" = "FetchError";
|
||||||
constructor(public cause: Error, msg?: string) {
|
constructor(public cause: Error, msg?: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RequiredError extends Error {
|
export class RequiredError extends Error {
|
||||||
override name: "RequiredError" = "RequiredError";
|
override name: "RequiredError" = "RequiredError";
|
||||||
constructor(public field: string, msg?: string) {
|
constructor(public field: string, msg?: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const COLLECTION_FORMATS = {
|
export const COLLECTION_FORMATS = {
|
||||||
csv: ",",
|
csv: ",",
|
||||||
ssv: " ",
|
ssv: " ",
|
||||||
tsv: "\t",
|
tsv: "\t",
|
||||||
pipes: "|",
|
pipes: "|",
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FetchAPI = WindowOrWorkerGlobalScope['fetch'];
|
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 type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise<RequestInit>
|
||||||
|
|
||||||
export interface FetchParams {
|
export interface FetchParams {
|
||||||
url: string;
|
url: string;
|
||||||
init: RequestInit;
|
init: RequestInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestOpts {
|
export interface RequestOpts {
|
||||||
path: string;
|
path: string;
|
||||||
method: HTTPMethod;
|
method: HTTPMethod;
|
||||||
headers: HTTPHeaders;
|
headers: HTTPHeaders;
|
||||||
query?: HTTPQuery;
|
query?: HTTPQuery;
|
||||||
body?: HTTPBody;
|
body?: HTTPBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function exists(json: any, key: string) {
|
export function exists(json: any, key: string) {
|
||||||
const value = json[key];
|
const value = json[key];
|
||||||
return value !== null && value !== undefined;
|
return value !== null && value !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function querystring(params: HTTPQuery, prefix: string = ''): string {
|
export function querystring(params: HTTPQuery, prefix: string = ''): string {
|
||||||
return Object.keys(params)
|
return Object.keys(params)
|
||||||
.map(key => querystringSingleKey(key, params[key], prefix))
|
.map(key => querystringSingleKey(key, params[key], prefix))
|
||||||
.filter(part => part.length > 0)
|
.filter(part => part.length > 0)
|
||||||
.join('&');
|
.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 {
|
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);
|
const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key);
|
||||||
if (value instanceof Array) {
|
if (value instanceof Array) {
|
||||||
const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue)))
|
const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue)))
|
||||||
.join(`&${encodeURIComponent(fullKey)}=`);
|
.join(`&${encodeURIComponent(fullKey)}=`);
|
||||||
return `${encodeURIComponent(fullKey)}=${multiValue}`;
|
return `${encodeURIComponent(fullKey)}=${multiValue}`;
|
||||||
}
|
}
|
||||||
if (value instanceof Set) {
|
if (value instanceof Set) {
|
||||||
const valueAsArray = Array.from(value);
|
const valueAsArray = Array.from(value);
|
||||||
return querystringSingleKey(key, valueAsArray, keyPrefix);
|
return querystringSingleKey(key, valueAsArray, keyPrefix);
|
||||||
}
|
}
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`;
|
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`;
|
||||||
}
|
}
|
||||||
if (value instanceof Object) {
|
if (value instanceof Object) {
|
||||||
return querystring(value as HTTPQuery, fullKey);
|
return querystring(value as HTTPQuery, fullKey);
|
||||||
}
|
}
|
||||||
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
|
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapValues(data: any, fn: (item: any) => any) {
|
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 {
|
export function canConsumeForm(consumes: Consume[]): boolean {
|
||||||
for (const consume of consumes) {
|
for (const consume of consumes) {
|
||||||
if ('multipart/form-data' === consume.contentType) {
|
if ('multipart/form-data' === consume.contentType) {
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Consume {
|
export interface Consume {
|
||||||
contentType: string;
|
contentType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestContext {
|
export interface RequestContext {
|
||||||
fetch: FetchAPI;
|
fetch: FetchAPI;
|
||||||
url: string;
|
url: string;
|
||||||
init: RequestInit;
|
init: RequestInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResponseContext {
|
export interface ResponseContext {
|
||||||
fetch: FetchAPI;
|
fetch: FetchAPI;
|
||||||
url: string;
|
url: string;
|
||||||
init: RequestInit;
|
init: RequestInit;
|
||||||
response: Response;
|
response: Response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ErrorContext {
|
export interface ErrorContext {
|
||||||
fetch: FetchAPI;
|
fetch: FetchAPI;
|
||||||
url: string;
|
url: string;
|
||||||
init: RequestInit;
|
init: RequestInit;
|
||||||
error: unknown;
|
error: unknown;
|
||||||
response?: Response;
|
response?: Response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Middleware {
|
export interface Middleware {
|
||||||
pre?(context: RequestContext): Promise<FetchParams | void>;
|
pre?(context: RequestContext): Promise<FetchParams | void>;
|
||||||
post?(context: ResponseContext): Promise<Response | void>;
|
post?(context: ResponseContext): Promise<Response | void>;
|
||||||
onError?(context: ErrorContext): Promise<Response | void>;
|
onError?(context: ErrorContext): Promise<Response | void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiResponse<T> {
|
export interface ApiResponse<T> {
|
||||||
raw: Response;
|
raw: Response;
|
||||||
value(): Promise<T>;
|
value(): Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResponseTransformer<T> {
|
export interface ResponseTransformer<T> {
|
||||||
(json: any): T;
|
(json: any): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JSONApiResponse<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> {
|
async value(): Promise<T> {
|
||||||
return this.transformer(await this.raw.json());
|
return this.transformer(await this.raw.json());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VoidApiResponse {
|
export class VoidApiResponse {
|
||||||
constructor(public raw: Response) {}
|
constructor(public raw: Response) { }
|
||||||
|
|
||||||
async value(): Promise<void> {
|
async value(): Promise<void> {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BlobApiResponse {
|
export class BlobApiResponse {
|
||||||
constructor(public raw: Response) {}
|
constructor(public raw: Response) { }
|
||||||
|
|
||||||
async value(): Promise<Blob> {
|
async value(): Promise<Blob> {
|
||||||
return await this.raw.blob();
|
return await this.raw.blob();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TextApiResponse {
|
export class TextApiResponse {
|
||||||
constructor(public raw: Response) {}
|
constructor(public raw: Response) { }
|
||||||
|
|
||||||
async value(): Promise<string> {
|
async value(): Promise<string> {
|
||||||
return await this.raw.text();
|
return await this.raw.text();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -130,10 +130,10 @@ export class ExtendedClient extends Client {
|
|||||||
for (const guild of guilds.values()) {
|
for (const guild of guilds.values()) {
|
||||||
logger.info("Starting background task for announcement role", { guildId: guild.id })
|
logger.info("Starting background task for announcement role", { guildId: guild.id })
|
||||||
const textChannel: Maybe<TextChannel> = this.getAnnouncementChannelForGuild(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 })
|
logger.error("Could not find announcement channel. Aborting", { guildId: guild.id })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.announcementRoleHandlerTask.set(guild.id, schedule("*/10 * * * * *", async () => {
|
this.announcementRoleHandlerTask.set(guild.id, schedule("*/10 * * * * *", async () => {
|
||||||
const requestId = uuid()
|
const requestId = uuid()
|
||||||
const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
|
const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
|
||||||
@ -174,7 +174,7 @@ export class ExtendedClient extends Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async startPollCloseBackgroundTasks() {
|
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])))
|
this.pollCloseBackgroundTasks.set(guild[1].id, schedule("0 * * * * *", () => checkForPollsToClose(guild[1])))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { CommandType } from "../types/commandTypes";
|
import { CommandType } from "../types/commandTypes";
|
||||||
|
|
||||||
export class Command {
|
export class Command {
|
||||||
constructor(commandOptions: CommandType) {
|
constructor(commandOptions: CommandType) {
|
||||||
Object.assign(this, commandOptions)
|
Object.assign(this, commandOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ApplicationCommandDataResolvable } from "discord.js"
|
import { ApplicationCommandDataResolvable } from "discord.js"
|
||||||
|
|
||||||
export interface RegisterCommandOptions {
|
export interface RegisterCommandOptions {
|
||||||
guildId?: string
|
guildId?: string
|
||||||
commands: ApplicationCommandDataResolvable[]
|
commands: ApplicationCommandDataResolvable[]
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,16 @@ import { PermissionResolvable, ChatInputApplicationCommandData, CommandInteracti
|
|||||||
import { ExtendedClient } from "../structures/client";
|
import { ExtendedClient } from "../structures/client";
|
||||||
|
|
||||||
export interface ExtendedInteraction extends CommandInteraction {
|
export interface ExtendedInteraction extends CommandInteraction {
|
||||||
member: GuildMember
|
member: GuildMember
|
||||||
}
|
}
|
||||||
export interface RunOptions {
|
export interface RunOptions {
|
||||||
client: ExtendedClient
|
client: ExtendedClient
|
||||||
interaction: ExtendedInteraction
|
interaction: ExtendedInteraction
|
||||||
args: CommandInteractionOptionResolver
|
args: CommandInteractionOptionResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunFunction = (options: RunOptions) => unknown
|
type RunFunction = (options: RunOptions) => unknown
|
||||||
export type CommandType = {
|
export type CommandType = {
|
||||||
userPermissions?: PermissionResolvable[]
|
userPermissions?: PermissionResolvable[]
|
||||||
run: RunFunction
|
run: RunFunction
|
||||||
} & ChatInputApplicationCommandData
|
} & ChatInputApplicationCommandData
|
||||||
|
@ -2,100 +2,100 @@ import { add } from "date-fns"
|
|||||||
import { CustomError, errorCodes } from "../interfaces"
|
import { CustomError, errorCodes } from "../interfaces"
|
||||||
|
|
||||||
export interface RepetitonInfo {
|
export interface RepetitonInfo {
|
||||||
startDate?: Date, // If defined will take precedence over repetitonAmount
|
startDate?: Date, // If defined will take precedence over repetitonAmount
|
||||||
endDate?: Date,// If defined will take precedence over repetitonAmount
|
endDate?: Date,// If defined will take precedence over repetitonAmount
|
||||||
totalAmount: number,
|
totalAmount: number,
|
||||||
alreadyOccured: number,
|
alreadyOccured: number,
|
||||||
schedule: Schedule
|
schedule: Schedule
|
||||||
}
|
}
|
||||||
export const scheduleNames = ['daily', 'weekly', 'monthly', 'everyNWeeks', 'everyNDays', 'everyNMonths']
|
export const scheduleNames = ['daily', 'weekly', 'monthly', 'everyNWeeks', 'everyNDays', 'everyNMonths']
|
||||||
export type supportedSchedule = typeof scheduleNames[number]
|
export type supportedSchedule = typeof scheduleNames[number]
|
||||||
export interface IScheduleType {
|
export interface IScheduleType {
|
||||||
name: supportedSchedule,
|
name: supportedSchedule,
|
||||||
multiplier: number,
|
multiplier: number,
|
||||||
duration: Duration
|
duration: Duration
|
||||||
}
|
}
|
||||||
export const scheduleTypes: IScheduleType[] = [
|
export const scheduleTypes: IScheduleType[] = [
|
||||||
{
|
{
|
||||||
name: 'daily',
|
name: 'daily',
|
||||||
multiplier: 1,
|
multiplier: 1,
|
||||||
duration: {
|
duration: {
|
||||||
days: 1
|
days: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'weekly',
|
name: 'weekly',
|
||||||
multiplier: 1,
|
multiplier: 1,
|
||||||
duration: {
|
duration: {
|
||||||
weeks: 1
|
weeks: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
export class Schedule {
|
export class Schedule {
|
||||||
private scheduleName: string
|
private scheduleName: string
|
||||||
private multiplier = 1
|
private multiplier = 1
|
||||||
private duration: Duration
|
private duration: Duration
|
||||||
private baseScheduleTypes = ['daily', 'weekly', 'monthly', 'yearly']
|
private baseScheduleTypes = ['daily', 'weekly', 'monthly', 'yearly']
|
||||||
private _scheduleString: string
|
private _scheduleString: string
|
||||||
constructor(scheduleString: string) {
|
constructor(scheduleString: string) {
|
||||||
this._scheduleString = scheduleString.toLowerCase()
|
this._scheduleString = scheduleString.toLowerCase()
|
||||||
this.scheduleName = this._scheduleString
|
this.scheduleName = this._scheduleString
|
||||||
if (this.baseScheduleTypes.includes(this._scheduleString)) {
|
if (this.baseScheduleTypes.includes(this._scheduleString)) {
|
||||||
this.multiplier = 1
|
this.multiplier = 1
|
||||||
}
|
}
|
||||||
if (this._scheduleString.includes('every')) {
|
if (this._scheduleString.includes('every')) {
|
||||||
this.scheduleName = this.getBaseScheduleNameFromVariableString()
|
this.scheduleName = this.getBaseScheduleNameFromVariableString()
|
||||||
this.multiplier = this.getMultiplierFromVariableString()
|
this.multiplier = this.getMultiplierFromVariableString()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this.scheduleName) {
|
switch (this.scheduleName) {
|
||||||
case 'daily':
|
case 'daily':
|
||||||
this.duration = { days: 1 }
|
this.duration = { days: 1 }
|
||||||
break
|
break
|
||||||
case 'weekly':
|
case 'weekly':
|
||||||
this.duration = { weeks: 1 }
|
this.duration = { weeks: 1 }
|
||||||
break
|
break
|
||||||
case 'monthly':
|
case 'monthly':
|
||||||
this.duration = { months: 1 }
|
this.duration = { months: 1 }
|
||||||
break
|
break
|
||||||
case 'yearly':
|
case 'yearly':
|
||||||
this.duration = { years: 1 }
|
this.duration = { years: 1 }
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new CustomError('Schedule type not supported', errorCodes.schedule_not_supported)
|
throw new CustomError('Schedule type not supported', errorCodes.schedule_not_supported)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public getSanitizedScheduleString(): string {
|
public getSanitizedScheduleString(): string {
|
||||||
return this._scheduleString
|
return this._scheduleString
|
||||||
}
|
}
|
||||||
private getBaseScheduleNameFromVariableString(): string {
|
private getBaseScheduleNameFromVariableString(): string {
|
||||||
if (this._scheduleString.includes('week')) return 'weekly'
|
if (this._scheduleString.includes('week')) return 'weekly'
|
||||||
if (this._scheduleString.includes('day')) return 'daily'
|
if (this._scheduleString.includes('day')) return 'daily'
|
||||||
if (this._scheduleString.includes('month')) return 'monthly'
|
if (this._scheduleString.includes('month')) return 'monthly'
|
||||||
if (this._scheduleString.includes('year')) return 'yearly'
|
if (this._scheduleString.includes('year')) return 'yearly'
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
public getMultiplierFromVariableString(): number {
|
public getMultiplierFromVariableString(): number {
|
||||||
const matches = this._scheduleString.match(/\d+/)
|
const matches = this._scheduleString.match(/\d+/)
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const multi = matches[0]
|
const multi = matches[0]
|
||||||
if (multi)
|
if (multi)
|
||||||
return parseInt(multi)
|
return parseInt(multi)
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
public calculateDuration(): Duration {
|
public calculateDuration(): Duration {
|
||||||
const dur: Duration = {
|
const dur: Duration = {
|
||||||
days: this.duration.days ? this.duration.days * this.multiplier : undefined,
|
days: this.duration.days ? this.duration.days * this.multiplier : undefined,
|
||||||
weeks: this.duration.weeks ? this.duration.weeks * this.multiplier : undefined,
|
weeks: this.duration.weeks ? this.duration.weeks * this.multiplier : undefined,
|
||||||
months: this.duration.months ? this.duration.months * this.multiplier : undefined,
|
months: this.duration.months ? this.duration.months * this.multiplier : undefined,
|
||||||
years: this.duration.years ? this.duration.years * this.multiplier : undefined,
|
years: this.duration.years ? this.duration.years * this.multiplier : undefined,
|
||||||
}
|
}
|
||||||
return dur
|
return dur
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNewDate(oldDate: Date): Date {
|
public getNewDate(oldDate: Date): Date {
|
||||||
const newDate = add(oldDate, this.calculateDuration())
|
const newDate = add(oldDate, this.calculateDuration())
|
||||||
return newDate
|
return newDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user