From e50cb10c5b7d0621410d22ecc923eb6397e5c9b6 Mon Sep 17 00:00:00 2001 From: Sammy Date: Mon, 12 Jun 2023 22:21:10 +0200 Subject: [PATCH 01/11] implement slash command for announcement --- server/commands/announce.ts | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 server/commands/announce.ts diff --git a/server/commands/announce.ts b/server/commands/announce.ts new file mode 100644 index 0000000..d825164 --- /dev/null +++ b/server/commands/announce.ts @@ -0,0 +1,38 @@ +import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, MessageEditOptions, TextChannel, messageLink } from 'discord.js' +import { v4 as uuid } from 'uuid' +import { config } from '../configuration' +import { Emotes } from '../events/guildScheduledEventCreate' +import { logger } from '../logger' +import { Command } from '../structures/command' +import { RunOptions } from '../types/commandTypes' +import { client } from '../..' + +export default new Command({ + name: 'announce', + description: 'Neues announcement im announcement Channel an alle senden.', + options: [], + run: async (interaction: RunOptions) => { + const command = interaction.interaction + const requestId = uuid() + const guildId = command.guildId! + logger.info("Got command for announcing!", { guildId, requestId }) + const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId) + + const body = `Hey! @everyone! Hier ist der Watchparty Bot vom Hartzarett. + +Wir machen in Zukunft regelmäßig Watchparties! Falls du mitmachen möchtest, reagiere einfach auf diesen Post mit 🎫, dann bekommst du automatisch eine Rolle zugewiesen und wirst benachrichtigt sobald es in der Zukunft weitere Watchparties und Filme zum abstimmen gibt. + +Für eine Erklärung wie das alles funktioniert mach einfach /mitgucken für eine lange Erklärung am Stück oder /guides wenn du auswählen möchtest wozu du Infos bekommst.` + + const options: MessageCreateOptions = { + allowedMentions: { parse: ['everyone'] }, + content: body + } + + const message: Message = await announcementChannel.send(options) + + message.react("🎫") + + command.followUp("Ist rausgeschickt!") + } +}) \ No newline at end of file From a2c55ad676ffb1cad9c1dd641ec0927dd4938cd1 Mon Sep 17 00:00:00 2001 From: Sammy Date: Mon, 12 Jun 2023 22:34:39 +0200 Subject: [PATCH 02/11] restrict announcements to admins --- server/commands/announce.ts | 43 ++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/server/commands/announce.ts b/server/commands/announce.ts index d825164..67897f4 100644 --- a/server/commands/announce.ts +++ b/server/commands/announce.ts @@ -1,4 +1,4 @@ -import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, MessageEditOptions, TextChannel, messageLink } from 'discord.js' +import { Guild, GuildMember, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, MessageEditOptions, TextChannel, messageLink } from 'discord.js' import { v4 as uuid } from 'uuid' import { config } from '../configuration' import { Emotes } from '../events/guildScheduledEventCreate' @@ -16,23 +16,36 @@ export default new Command({ const requestId = uuid() const guildId = command.guildId! logger.info("Got command for announcing!", { guildId, requestId }) - const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId) - const body = `Hey! @everyone! Hier ist der Watchparty Bot vom Hartzarett. - + 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`) + } + sendAnnouncement(guildId, requestId) + command.followUp("Ist rausgeschickt!") + } +}) + +function isAdmin(member: GuildMember): boolean { + return member.roles.cache.find((role, _) => role.id === config.bot.jf_admin_role) != undefined +} + +async function sendAnnouncement(guildId: string, requestId: string): Promise { + logger.info("Sending announcement") + const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId) + + const body = `Hey! @everyone! Hier ist der Watchparty Bot vom Hartzarett. + Wir machen in Zukunft regelmäßig Watchparties! Falls du mitmachen möchtest, reagiere einfach auf diesen Post mit 🎫, dann bekommst du automatisch eine Rolle zugewiesen und wirst benachrichtigt sobald es in der Zukunft weitere Watchparties und Filme zum abstimmen gibt. Für eine Erklärung wie das alles funktioniert mach einfach /mitgucken für eine lange Erklärung am Stück oder /guides wenn du auswählen möchtest wozu du Infos bekommst.` - const options: MessageCreateOptions = { - allowedMentions: { parse: ['everyone'] }, - content: body - } - - const message: Message = await announcementChannel.send(options) - - message.react("🎫") - - command.followUp("Ist rausgeschickt!") + const options: MessageCreateOptions = { + allowedMentions: { parse: ['everyone'] }, + content: body } -}) \ No newline at end of file + const message: Message = await announcementChannel.send(options) + message.react("🎫") +} \ No newline at end of file From 24754decf47dd4f4b5da1930698465cd46b437df Mon Sep 17 00:00:00 2001 From: Sammy Date: Tue, 13 Jun 2023 18:18:26 +0200 Subject: [PATCH 03/11] implement announcement role management by reaction --- server/commands/announce.ts | 65 +++++++++++++++++++++++++++++++++---- server/configuration.ts | 2 ++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/server/commands/announce.ts b/server/commands/announce.ts index 67897f4..328855e 100644 --- a/server/commands/announce.ts +++ b/server/commands/announce.ts @@ -1,11 +1,14 @@ -import { Guild, GuildMember, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, MessageEditOptions, TextChannel, messageLink } from 'discord.js' +import { Guild, GuildMember, Message, MessageCreateOptions, MessageReaction, Role, TextChannel, User } from 'discord.js' import { v4 as uuid } from 'uuid' +import { client } from '../..' import { config } from '../configuration' -import { Emotes } from '../events/guildScheduledEventCreate' import { logger } from '../logger' import { Command } from '../structures/command' import { RunOptions } from '../types/commandTypes' -import { client } from '../..' +import { off } from 'process' +import { ScheduledTask, schedule } from 'node-cron' + +let task: ScheduledTask export default new Command({ name: 'announce', @@ -29,7 +32,7 @@ export default new Command({ }) 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 sendAnnouncement(guildId: string, requestId: string): Promise { @@ -47,5 +50,55 @@ Für eine Erklärung wie das alles funktioniert mach einfach /mitgucken für ein content: body } const message: Message = await announcementChannel.send(options) - message.react("🎫") -} \ No newline at end of file + await message.react("🎫") + // await message.pin() + + task = schedule("* * * * * *", async () => { + const reactions = await message.reactions.resolve("🎫") + if (reactions) { + manageAnnouncementRoles(message.guild, reactions) + } else { + logger.error("Did not get reactions! Aborting!", { guildId, requestId }) + } + }) +} + +async function manageAnnouncementRoles(guild: Guild, reaction: MessageReaction) { + const requestId = uuid() + const guildId = guild.id + logger.info("Managing roles", { guildId, requestId }) + + const announcementRole: Role | undefined = (await guild.roles.fetch()).find(role => role.id === config.bot.announcement_role) + if (!announcementRole) { + logger.error(`Could not find announcement role! Aborting! Was looking for role with id: ${config.bot.announcement_role}`, { guildId, requestId }) + return + } + + const usersWhoWantRole: User[] = (await reaction.users.fetch()).filter(user => !user.bot).map(user => user) + + const allUsers = (await guild.members.fetch()) + + const usersWhoHaveRole: GuildMember[] = allUsers + .filter(member=> member.roles.cache + .find(role => role.id === config.bot.announcement_role) !== undefined) + .map(member => member) + + const usersWhoNeedRoleRevoked: GuildMember[] = usersWhoHaveRole + .filter(userWhoHas => !usersWhoWantRole.map(wanter => wanter.id).includes(userWhoHas.id)) + + const usersWhoDontHaveRole: GuildMember[] = allUsers + .filter(member => member.roles.cache + .find(role=> role.id === config.bot.announcement_role) === undefined) + .map(member => member) + + const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole + .filter(userWhoNeeds => usersWhoWantRole.map(wanter => wanter.id).includes(userWhoNeeds.id)) + + + logger.debug(`Theses users will get the role removed: ${JSON.stringify(usersWhoNeedRoleRevoked)}`, {guildId, requestId}) + logger.debug(`Theses users will get the role added: ${JSON.stringify(usersWhoNeedRole)}`, {guildId, requestId}) + + usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole)) + usersWhoNeedRole.forEach(user => user.roles.add(announcementRole)) +} + diff --git a/server/configuration.ts b/server/configuration.ts index 5807c47..144901b 100644 --- a/server/configuration.ts +++ b/server/configuration.ts @@ -23,6 +23,7 @@ export interface Config { workaround_token: string watcher_role: string jf_admin_role: string + announcement_role: string announcement_channel_id: string jf_collection_id: string } @@ -57,6 +58,7 @@ export const config: Config = { workaround_token: process.env.TOKEN ?? "", watcher_role: process.env.WATCHER_ROLE ?? "", jf_admin_role: process.env.ADMIN_ROLE ?? "", + announcement_role: process.env.WATCHPARTY_ANNOUNCEMENT_ROLE ?? "", announcement_channel_id: process.env.CHANNEL_ID ?? "", jf_collection_id: process.env.JELLYFIN_COLLECTION_ID ?? "" } From e774474a554cd574285947dffa2f442fe87144a1 Mon Sep 17 00:00:00 2001 From: Sammy Date: Tue, 13 Jun 2023 18:58:41 +0200 Subject: [PATCH 04/11] Put role handling in background task scheduled at startup --- server/commands/announce.ts | 19 ++++++---------- server/structures/client.ts | 45 +++++++++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/server/commands/announce.ts b/server/commands/announce.ts index 328855e..631351b 100644 --- a/server/commands/announce.ts +++ b/server/commands/announce.ts @@ -39,7 +39,11 @@ async function sendAnnouncement(guildId: string, requestId: string): Promise message.cleanContent.includes("[announcement]")) + currentPinnedAnnouncementMessages.forEach(async (message) => await message.unpin()) + currentPinnedAnnouncementMessages.forEach(message => message.delete()) + + const body = `[announcement] Hey! @everyone! Hier ist der Watchparty Bot vom Hartzarett. Wir machen in Zukunft regelmäßig Watchparties! 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. @@ -51,20 +55,11 @@ Für eine Erklärung wie das alles funktioniert mach einfach /mitgucken für ein } const message: Message = await announcementChannel.send(options) await message.react("🎫") - // await message.pin() + await message.pin() - task = schedule("* * * * * *", async () => { - const reactions = await message.reactions.resolve("🎫") - if (reactions) { - manageAnnouncementRoles(message.guild, reactions) - } else { - logger.error("Did not get reactions! Aborting!", { guildId, requestId }) - } - }) } -async function manageAnnouncementRoles(guild: Guild, reaction: MessageReaction) { - const requestId = uuid() +export async function manageAnnouncementRoles(guild: Guild, reaction: MessageReaction, requestId: string) { const guildId = guild.id logger.info("Managing roles", { guildId, requestId }) diff --git a/server/structures/client.ts b/server/structures/client.ts index d0b58cb..3ab1e63 100644 --- a/server/structures/client.ts +++ b/server/structures/client.ts @@ -4,6 +4,10 @@ import fs from 'fs' import { config } from "../configuration"; import { logger } from "../logger"; import { JellyfinHandler } from "../jellyfin/handler"; +import { ScheduledTask, schedule } from "node-cron"; +import { manageAnnouncementRoles } from "../commands/announce"; +import { v4 as uuid } from 'uuid' +import { task } from "../events/guildScheduledEventCreate"; @@ -13,6 +17,7 @@ export class ExtendedClient extends Client { private jellyfin: JellyfinHandler public commands: Collection = new Collection() private announcementChannels: Collection = new Collection //guildId to TextChannel + private announcementRoleHandlerTask: Collection = new Collection //one task per guild public constructor(jf: JellyfinHandler) { const intents: IntentsBitField = new IntentsBitField() intents.add(IntentsBitField.Flags.GuildMembers, IntentsBitField.Flags.MessageContent, IntentsBitField.Flags.Guilds, IntentsBitField.Flags.DirectMessages, IntentsBitField.Flags.GuildScheduledEvents, IntentsBitField.Flags.GuildVoiceStates) @@ -60,14 +65,15 @@ export class ExtendedClient extends Client { this.commands.set(command.name, command) slashCommands.push(command) } - this.on("ready", (client: Client) => { + this.on("ready", async (client: Client) => { //logger.info(`Ready processing ${JSON.stringify(client)}`) logger.info(`SlashCommands: ${JSON.stringify(slashCommands)}`) const guilds = client.guilds.cache this.registerCommands(slashCommands, guilds) this.cacheUsers(guilds) - this.cacheAnnouncementServer(guilds) + await this.cacheAnnouncementServer(guilds) + this.startAnnouncementRoleBackgroundTask(guilds) }) } catch (error) { logger.info(`Error refreshing slash commands: ${error}`) @@ -117,4 +123,39 @@ export class ExtendedClient extends Client { logger.error(error) } } + + public async startAnnouncementRoleBackgroundTask(guilds: Collection) { + for (const guild of guilds.values()) { + logger.info("Starting background task for announcement role", { guildId: guild.id }) + const textChannel: TextChannel = this.getAnnouncementChannelForGuild(guild.id) + this.announcementRoleHandlerTask.set(guild.id, schedule("*/10 * * * * *", async () => { + const requestId = uuid() + const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[announcement]")) + + if (messages.size > 1) { + logger.error("More than one pinned announcement Messages found. Unable to know which one people react to. Please fix!", { guildId: guild.id, requestId }) + } else if (messages.size == 0) { + logger.error("Could not find any pinned announcement messages. Unable to manage roles!", { guildId: guild.id, requestId }) + } + + const message = messages.at(0)! + + const reactions = await message.reactions.resolve("🎫") + if (reactions) { + manageAnnouncementRoles(message.guild, reactions, requestId) + } else { + logger.error("Did not get reactions! Aborting!", { guildId: guild.id, requestId }) + } + })) + } + } + public stopAnnouncementRoleBackgroundTask(guild: string | Guild, requestId: string) { + const guildId: string = guild instanceof Guild ? guild.id : guild + const task: ScheduledTask | undefined = guild instanceof Guild ? this.announcementRoleHandlerTask.get(guild.id) : this.announcementRoleHandlerTask.get(guild) + if (!task) { + logger.error(`No task found for guildID ${guildId}.`, { guildId, requestId }) + return + } + task.stop() + } } From a5eab2f7be5c6d3ac0e4d481b301f89ed72f6cd9 Mon Sep 17 00:00:00 2001 From: Sammy Date: Tue, 13 Jun 2023 20:13:13 +0200 Subject: [PATCH 05/11] fix bug that reactions are not loaded after restart the message needed to be fetched again. Probably something with caches.. --- server/structures/client.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/structures/client.ts b/server/structures/client.ts index 3ab1e63..ce37605 100644 --- a/server/structures/client.ts +++ b/server/structures/client.ts @@ -130,7 +130,7 @@ export class ExtendedClient extends Client { const textChannel: TextChannel = this.getAnnouncementChannelForGuild(guild.id) this.announcementRoleHandlerTask.set(guild.id, schedule("*/10 * * * * *", async () => { const requestId = uuid() - const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[announcement]")) + const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]")) if (messages.size > 1) { logger.error("More than one pinned announcement Messages found. Unable to know which one people react to. Please fix!", { guildId: guild.id, requestId }) @@ -138,9 +138,11 @@ export class ExtendedClient extends Client { logger.error("Could not find any pinned announcement messages. Unable to manage roles!", { guildId: guild.id, requestId }) } - const message = messages.at(0)! + const message = await messages.at(0)!.fetch() + //logger.debug(`Message: ${JSON.stringify(message, null, 2)}`, { guildId: guild.id, requestId }) - const reactions = await message.reactions.resolve("🎫") + const reactions = message.reactions.resolve("🎫") + //logger.debug(`reactions: ${JSON.stringify(reactions, null, 2)}`, { guildId: guild.id, requestId }) if (reactions) { manageAnnouncementRoles(message.guild, reactions, requestId) } else { @@ -149,6 +151,7 @@ export class ExtendedClient extends Client { })) } } + public stopAnnouncementRoleBackgroundTask(guild: string | Guild, requestId: string) { const guildId: string = guild instanceof Guild ? guild.id : guild const task: ScheduledTask | undefined = guild instanceof Guild ? this.announcementRoleHandlerTask.get(guild.id) : this.announcementRoleHandlerTask.get(guild) From baefcf9bb9976080ddab893f170965004b9d1386 Mon Sep 17 00:00:00 2001 From: Sammy Date: Tue, 13 Jun 2023 21:12:32 +0200 Subject: [PATCH 06/11] add options for announcements --- server/commands/announce.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/server/commands/announce.ts b/server/commands/announce.ts index 631351b..4fac196 100644 --- a/server/commands/announce.ts +++ b/server/commands/announce.ts @@ -1,4 +1,4 @@ -import { Guild, GuildMember, Message, MessageCreateOptions, MessageReaction, Role, TextChannel, User } from 'discord.js' +import { ApplicationCommandOptionType, Guild, GuildMember, Message, MessageCreateOptions, MessageReaction, Role, TextChannel, User } from 'discord.js' import { v4 as uuid } from 'uuid' import { client } from '../..' import { config } from '../configuration' @@ -13,12 +13,24 @@ let task: ScheduledTask export default new Command({ name: 'announce', description: 'Neues announcement im announcement Channel an alle senden.', - options: [], + options: [{ + name: "typ", + type: ApplicationCommandOptionType.String, + description:"Was für ein announcement?", + choices: [{name: "initial", value:"initial"},{name: "votepls", value:"votepls"},{name: "cancel", value:"cancel"}], + required: true + }], run: async (interaction: RunOptions) => { const command = interaction.interaction const requestId = uuid() const guildId = command.guildId! - logger.info("Got command for announcing!", { guildId, requestId }) + 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 }) @@ -26,8 +38,13 @@ export default new Command({ } else { logger.info(`User ${command.member.displayName} seems to be admin`) } - sendAnnouncement(guildId, requestId) - command.followUp("Ist rausgeschickt!") + + if((announcementType.value).includes("initial")) { + sendInitialAnnouncement(guildId, requestId) + command.followUp("Ist rausgeschickt!") + } else { + command.followUp(`${announcementType.value} ist aktuell noch nicht implementiert`) + } } }) @@ -35,15 +52,15 @@ function isAdmin(member: GuildMember): boolean { return member.roles.cache.find((role, _) => role.id === config.bot.jf_admin_role) !== undefined } -async function sendAnnouncement(guildId: string, requestId: string): Promise { - logger.info("Sending announcement") +async function sendInitialAnnouncement(guildId: string, requestId: string): Promise { + logger.info("Sending initial announcement") const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId) - const currentPinnedAnnouncementMessages = (await announcementChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[announcement]")) + const currentPinnedAnnouncementMessages = (await announcementChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]")) currentPinnedAnnouncementMessages.forEach(async (message) => await message.unpin()) currentPinnedAnnouncementMessages.forEach(message => message.delete()) - const body = `[announcement] 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! 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. From 198a25d145eab98f5d9af679c3148310ed200609 Mon Sep 17 00:00:00 2001 From: Sammy Date: Tue, 13 Jun 2023 23:15:03 +0200 Subject: [PATCH 07/11] ping watch role when voting starts and closes --- server/commands/closepoll.ts | 5 +++-- server/events/guildScheduledEventCreate.ts | 12 +++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/server/commands/closepoll.ts b/server/commands/closepoll.ts index 1290882..6997726 100644 --- a/server/commands/closepoll.ts +++ b/server/commands/closepoll.ts @@ -61,15 +61,16 @@ export async function closePoll(guild: Guild, requestId: string) { } async function updateMessage(movie: string, message: Message, guildId: string, requestId: string) { + const role = (await message.guild!.roles.fetch()).find(role => role.id === config.bot.announcement_role) const body = `[Abstimmung beendet] Gewonnen hat: ${movie}` - .concat(message.cleanContent.substring("[Abstimmung]".length)) + .concat(message.content.substring("[Abstimmung]".length)) const options: MessageEditOptions = { content: body, + allowedMentions: { parse: ["roles"] } } logger.info("Updating message.", { guildId, requestId }) message.edit(options) - } async function updateEvent(votes: Vote[], guild: Guild, guildId: string, requestId: string) { diff --git a/server/events/guildScheduledEventCreate.ts b/server/events/guildScheduledEventCreate.ts index bdee85b..3ac411d 100644 --- a/server/events/guildScheduledEventCreate.ts +++ b/server/events/guildScheduledEventCreate.ts @@ -1,4 +1,4 @@ -import { GuildScheduledEvent, Message, TextChannel } from "discord.js"; +import { GuildScheduledEvent, Message, MessageCreateOptions, TextChannel } from "discord.js"; import { ScheduledTask, schedule } from "node-cron"; import { v4 as uuid } from "uuid"; import { client, jellyfinHandler } from "../.."; @@ -30,14 +30,20 @@ export async function execute(event: GuildScheduledEvent) { const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(event.guildId) logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId }) + const role = (await event.guild!.roles.fetch()).find(role => role.id === config.bot.announcement_role) - let message = "[Abstimmung]\nEs gibt eine neue Abstimmung für die nächste Watchparty! Stimme hierunter für den nächsten Film ab!\n" + let message = `[Abstimmung]\n<@&${role ? role.id : "hab die Rolle nicht gefunden"}> Es gibt eine neue Abstimmung für die nächste Watchparty! 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].name!).concat("\n") } - const sentMessage: Message = await (await announcementChannel.fetch()).send(message) + const options: MessageCreateOptions = { + allowedMentions: { parse: ["roles"]}, + content: message + } + + const sentMessage: Message = await (await announcementChannel.fetch()).send(options) for (let i = 0; i < movies.length; i++) { sentMessage.react(Emotes[i]) From 220f9dc8ef7ab83bfb6e23f9589faab8b8ade77f Mon Sep 17 00:00:00 2001 From: Sammy Date: Wed, 14 Jun 2023 19:45:33 +0200 Subject: [PATCH 08/11] undo fetching role to get roleID We already have the role id?!?!? --- server/events/guildScheduledEventCreate.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/events/guildScheduledEventCreate.ts b/server/events/guildScheduledEventCreate.ts index 3ac411d..7c160f2 100644 --- a/server/events/guildScheduledEventCreate.ts +++ b/server/events/guildScheduledEventCreate.ts @@ -30,9 +30,8 @@ export async function execute(event: GuildScheduledEvent) { const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(event.guildId) logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId }) - const role = (await event.guild!.roles.fetch()).find(role => role.id === config.bot.announcement_role) - let message = `[Abstimmung]\n<@&${role ? role.id : "hab die Rolle nicht gefunden"}> Es gibt eine neue Abstimmung für die nächste Watchparty! Stimme hierunter für den nächsten Film ab!\n` + let message = `[Abstimmung]\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty! 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].name!).concat("\n") From 9420eb436655a2bfd8771ddcfb043ea85df17ac4 Mon Sep 17 00:00:00 2001 From: Sammy Date: Wed, 14 Jun 2023 22:24:39 +0200 Subject: [PATCH 09/11] Change announcements All announcements but initial will be deleted upon event end. Vote announcement will be deleted upon vote end Vote and vote end announcement now contain date and time --- server/commands/announce.ts | 6 ++- server/commands/closepoll.ts | 63 ++++++++++++---------- server/events/guildScheduledEventCreate.ts | 10 +++- server/events/guildScheduledEventUpdate.ts | 5 +- 4 files changed, 53 insertions(+), 31 deletions(-) diff --git a/server/commands/announce.ts b/server/commands/announce.ts index 4fac196..28d7562 100644 --- a/server/commands/announce.ts +++ b/server/commands/announce.ts @@ -23,7 +23,11 @@ export default new Command({ run: async (interaction: RunOptions) => { const command = interaction.interaction const requestId = uuid() - const guildId = command.guildId! + if(!command.guildId) { + logger.error("COMMAND DOES NOT HAVE A GUILD ID; CANCELLING!!!", {requestId}) + return + } + const guildId = command.guildId const announcementType = command.options.data.find(option => option.name.includes("typ")) logger.info(`Got command for announcing ${announcementType?.value}!`, { guildId, requestId }) diff --git a/server/commands/closepoll.ts b/server/commands/closepoll.ts index 6997726..511afa5 100644 --- a/server/commands/closepoll.ts +++ b/server/commands/closepoll.ts @@ -1,11 +1,12 @@ -import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageEditOptions, TextChannel } from 'discord.js' +import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, TextChannel } from 'discord.js' import { v4 as uuid } from 'uuid' +import { client } from '../..' import { config } from '../configuration' import { Emotes } from '../events/guildScheduledEventCreate' import { logger } from '../logger' import { Command } from '../structures/command' import { RunOptions } from '../types/commandTypes' -import { client } from '../..' +import { format } from 'date-fns' export default new Command({ name: 'closepoll', @@ -49,43 +50,38 @@ export async function closePoll(guild: Guild, requestId: string) { logger.debug(`Last message: ${JSON.stringify(lastMessage, null, 2)}`, { guildId, requestId }) + const votes = await (await getVotesByEmote(lastMessage, guildId, requestId)) - .sort((a, b) => b.count - a.count) - + .sort((a, b) => b.count - a.count) + logger.debug(`votes: ${JSON.stringify(votes, null, 2)}`, { guildId, requestId }) - - updateEvent(votes, guild!, guildId, requestId) - updateMessage(votes[0].movie, lastMessage, guildId, requestId) + + logger.info("Deleting vote message") + await lastMessage.delete() + const event = await getEvent(guild, guild.id, requestId) + if(event) { + updateEvent(event, votes, guild!, guildId, requestId) + sendVoteClosedMessage(event, votes[0].movie, guildId, requestId) + } //lastMessage.unpin() //todo: uncomment when bot has permission to pin/unpin } -async function updateMessage(movie: string, message: Message, guildId: string, requestId: string) { - const role = (await message.guild!.roles.fetch()).find(role => role.id === config.bot.announcement_role) - const body = `[Abstimmung beendet] Gewonnen hat: ${movie}` - .concat(message.content.substring("[Abstimmung]".length)) - - const options: MessageEditOptions = { +async function sendVoteClosedMessage(event: GuildScheduledEvent, movie: string, guildId: string, requestId: string) { + const date = event.scheduledStartAt ? format(event.scheduledStartAt, "dd.MM") : "Fehler, event hatte kein Datum" + const time = event.scheduledStartAt ? format(event.scheduledStartAt, "HH:mm") : "Fehler, event hatte kein Datum" + const body = `[Abstimmung beendet] <@&${config.bot.announcement_role}> Wir gucken ${movie} am ${date} um ${time}` + const options: MessageCreateOptions = { content: body, allowedMentions: { parse: ["roles"] } } - logger.info("Updating message.", { guildId, requestId }) - message.edit(options) + const announcementChannel = client.getAnnouncementChannelForGuild(guildId) + logger.info("Sending vote closed message.", { guildId, requestId }) + announcementChannel.send(options) } -async function updateEvent(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 }) - const voteEvents = (await guild.scheduledEvents.fetch()) - .map((value, _) => value) - .filter(event => event.name.toLowerCase().includes("voting offen")) - logger.debug(`Found events: ${JSON.stringify(voteEvents, null, 2)}`, { guildId, requestId }) - - if (!voteEvents || voteEvents.length <= 0) { - logger.error("Could not find vote event. Cancelling update!", { guildId, requestId }) - return - } - - const voteEvent: GuildScheduledEvent = voteEvents[0] const options: GuildScheduledEventEditOptions> = { 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` @@ -95,6 +91,19 @@ async function updateEvent(votes: Vote[], guild: Guild, guildId: string, request voteEvent.edit(options) } +async function getEvent(guild: Guild, guildId: string, requestId: string): Promise { + const voteEvents = (await guild.scheduledEvents.fetch()) + .map((value, _) => value) + .filter(event => event.name.toLowerCase().includes("voting offen")) + logger.debug(`Found events: ${JSON.stringify(voteEvents, null, 2)}`, { guildId, requestId }) + + if (!voteEvents || voteEvents.length <= 0) { + logger.error("Could not find vote event. Cancelling update!", { guildId, requestId }) + return null + } + return voteEvents[0] +} + type Vote = { emote: string, //todo habs nicht hinbekommen hier Emotes zu nutzen count: number, diff --git a/server/events/guildScheduledEventCreate.ts b/server/events/guildScheduledEventCreate.ts index 7c160f2..7b818bc 100644 --- a/server/events/guildScheduledEventCreate.ts +++ b/server/events/guildScheduledEventCreate.ts @@ -6,7 +6,7 @@ import { closePoll } from "../commands/closepoll"; import { config } from "../configuration"; import { logger } from "../logger"; import toDate from "date-fns/fp/toDate"; -import { addDays, isAfter, isBefore } from "date-fns"; +import { addDays, format, isAfter, isBefore } from "date-fns"; export const name = 'guildScheduledEventCreate' @@ -31,7 +31,13 @@ export async function execute(event: GuildScheduledEvent) { const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(event.guildId) logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId }) - let message = `[Abstimmung]\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty! Stimme hierunter für den nächsten Film ab!\n` + if(!event.scheduledStartAt) { + logger.info("EVENT DOES NOT HAVE STARTDATE; CANCELLING", {guildId: event.guildId, requestId}) + return + } + const date = format(event.scheduledStartAt, "dd.MM") + const time = format(event.scheduledStartAt, "HH:mm") + let message = `[Abstimmung]\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty am ${date} um ${time}}! 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].name!).concat("\n") diff --git a/server/events/guildScheduledEventUpdate.ts b/server/events/guildScheduledEventUpdate.ts index b479101..f183740 100644 --- a/server/events/guildScheduledEventUpdate.ts +++ b/server/events/guildScheduledEventUpdate.ts @@ -1,8 +1,9 @@ import { GuildMember, GuildScheduledEvent, GuildScheduledEventStatus } from "discord.js"; import { v4 as uuid } from "uuid"; -import { jellyfinHandler } from "../.."; +import { client, jellyfinHandler } from "../.."; import { getGuildSpecificTriggerRoleId } from "../helper/roleFilter"; import { logger } from "../logger"; +import { manageAnnouncementRoles } from "../commands/announce"; export const name = 'guildScheduledEventUpdate' @@ -27,6 +28,8 @@ export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildSche if (newEvent.status === GuildScheduledEventStatus.Active) createJFUsers(members, newEvent.name, requestId) else { + const announcements = (await client.getAnnouncementChannelForGuild(newEvent.guild!.id).messages.fetch()).filter(message => !message.pinned) + announcements.forEach(message => message.delete()) 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.`)) }) From 5b99c843b4ea2292b0c1984310723a344afa3506 Mon Sep 17 00:00:00 2001 From: Sammy Date: Thu, 15 Jun 2023 21:56:15 +0200 Subject: [PATCH 10/11] Fix PR and linting issues --- server/commands/announce.ts | 13 ++++--- server/commands/closepoll.ts | 29 ++++++++++----- server/commands/resetPassword.ts | 5 +-- server/configuration.ts | 1 - server/events/guildScheduledEventCreate.ts | 15 +++++--- server/events/guildScheduledEventUpdate.ts | 26 ++++++++----- server/events/ready.ts | 4 -- server/events/voiceStateUpdate.ts | 4 +- server/helper/roleFilter.ts | 2 +- server/jellyfin/handler.ts | 8 +--- server/structures/client.ts | 43 +++++++++++++--------- 11 files changed, 86 insertions(+), 64 deletions(-) delete mode 100644 server/events/ready.ts diff --git a/server/commands/announce.ts b/server/commands/announce.ts index 28d7562..85f3b43 100644 --- a/server/commands/announce.ts +++ b/server/commands/announce.ts @@ -2,13 +2,10 @@ import { ApplicationCommandOptionType, Guild, GuildMember, Message, MessageCreat import { v4 as uuid } from 'uuid' import { client } from '../..' import { config } from '../configuration' +import { Maybe } from '../interfaces' import { logger } from '../logger' import { Command } from '../structures/command' import { RunOptions } from '../types/commandTypes' -import { off } from 'process' -import { ScheduledTask, schedule } from 'node-cron' - -let task: ScheduledTask export default new Command({ name: 'announce', @@ -53,12 +50,16 @@ export default new Command({ }) 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 { logger.info("Sending initial announcement") - const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId) + const announcementChannel: Maybe = client.getAnnouncementChannelForGuild(guildId) + if(!announcementChannel) { + logger.error("Could not find announcement channel. Aborting", { guildId, requestId }) + return + } const currentPinnedAnnouncementMessages = (await announcementChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]")) currentPinnedAnnouncementMessages.forEach(async (message) => await message.unpin()) diff --git a/server/commands/closepoll.ts b/server/commands/closepoll.ts index 511afa5..b254f38 100644 --- a/server/commands/closepoll.ts +++ b/server/commands/closepoll.ts @@ -7,6 +7,7 @@ import { logger } from '../logger' import { Command } from '../structures/command' import { RunOptions } from '../types/commandTypes' import { format } from 'date-fns' +import { Maybe } from '../interfaces' export default new Command({ name: 'closepoll', @@ -15,14 +16,14 @@ export default new Command({ run: async (interaction: RunOptions) => { const command = interaction.interaction const requestId = uuid() - const guildId = command.guildId! - logger.info("Got command for closing poll!", { guildId, requestId }) if (!command.guild) { - logger.error("No guild found in interaction. Cancelling closing request", { guildId, 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 :(") 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) } @@ -32,10 +33,14 @@ export async function closePoll(guild: Guild, requestId: string) { const guildId = guild.id logger.info("stopping poll", { guildId, requestId }) - const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId) + const announcementChannel: Maybe = client.getAnnouncementChannelForGuild(guildId) + if(!announcementChannel) { + logger.error("Could not find the textchannel. Unable to close poll.", { guildId, requestId }) + return + } const messages: Message[] = (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]")) .sort((a, b) => b.createdTimestamp - a.createdTimestamp) @@ -60,7 +65,7 @@ export async function closePoll(guild: Guild, requestId: string) { await lastMessage.delete() const event = await getEvent(guild, guild.id, requestId) if(event) { - updateEvent(event, votes, guild!, guildId, requestId) + updateEvent(event, votes, guild, guildId, requestId) sendVoteClosedMessage(event, votes[0].movie, guildId, requestId) } @@ -77,6 +82,10 @@ async function sendVoteClosedMessage(event: GuildScheduledEvent, movie: string, } const announcementChannel = client.getAnnouncementChannelForGuild(guildId) logger.info("Sending vote closed message.", { guildId, requestId }) + if(!announcementChannel) { + logger.error("Could not find announcement channel. Please fix!", { guildId, requestId }) + return + } announcementChannel.send(options) } @@ -93,7 +102,7 @@ async function updateEvent(voteEvent: GuildScheduledEvent, votes: Vote[], guild: async function getEvent(guild: Guild, guildId: string, requestId: string): Promise { const voteEvents = (await guild.scheduledEvents.fetch()) - .map((value, _) => value) + .map((value) => value) .filter(event => event.name.toLowerCase().includes("voting offen")) logger.debug(`Found events: ${JSON.stringify(voteEvents, null, 2)}`, { guildId, requestId }) @@ -119,14 +128,14 @@ async function getVotesByEmote(message: Message, guildId: string, requestId: str const reaction = await message.reactions.resolve(emote) logger.debug(`Reaction for emote ${emote}: ${JSON.stringify(reaction, null, 2)}`, { guildId, requestId }) if (reaction) { - const vote: Vote = { emote: emote, count: reaction.count, movie: extractMovieFromMessageByEmote(message, emote, guildId, requestId) } + const vote: Vote = { emote: emote, count: reaction.count, movie: extractMovieFromMessageByEmote(message, emote) } votes.push(vote) } } return votes } -function extractMovieFromMessageByEmote(message: Message, emote: string, guildId: string, requestId: string): string { +function extractMovieFromMessageByEmote(message: Message, emote: string): string { const lines = message.cleanContent.split("\n") const emoteLines = lines.filter(line => line.includes(emote)) diff --git a/server/commands/resetPassword.ts b/server/commands/resetPassword.ts index 479e819..28eac2c 100644 --- a/server/commands/resetPassword.ts +++ b/server/commands/resetPassword.ts @@ -1,8 +1,7 @@ -import { ApplicationCommandOptionType, BurstHandlerMajorIdKey } from 'discord.js' +import { v4 as uuid } from 'uuid' +import { jellyfinHandler } from "../.." import { Command } from '../structures/command' import { RunOptions } from '../types/commandTypes' -import { jellyfinHandler } from "../.." -import { v4 as uuid } from 'uuid' export default new Command({ name: 'passwort_reset', diff --git a/server/configuration.ts b/server/configuration.ts index 144901b..1ad75d6 100644 --- a/server/configuration.ts +++ b/server/configuration.ts @@ -1,5 +1,4 @@ import dotenv from "dotenv" -import { AddListingProviderRequestToJSON } from "./jellyfin" dotenv.config() interface options { diff --git a/server/events/guildScheduledEventCreate.ts b/server/events/guildScheduledEventCreate.ts index 7b818bc..7d36221 100644 --- a/server/events/guildScheduledEventCreate.ts +++ b/server/events/guildScheduledEventCreate.ts @@ -1,3 +1,5 @@ +import { addDays, format, isAfter } from "date-fns"; +import toDate from "date-fns/fp/toDate"; import { GuildScheduledEvent, Message, MessageCreateOptions, TextChannel } from "discord.js"; import { ScheduledTask, schedule } from "node-cron"; import { v4 as uuid } from "uuid"; @@ -5,8 +7,7 @@ import { client, jellyfinHandler } from "../.."; import { closePoll } from "../commands/closepoll"; import { config } from "../configuration"; import { logger } from "../logger"; -import toDate from "date-fns/fp/toDate"; -import { addDays, format, isAfter, isBefore } from "date-fns"; +import { Maybe } from "../interfaces"; export const name = 'guildScheduledEventCreate' @@ -28,7 +29,11 @@ export async function execute(event: GuildScheduledEvent) { logger.info(`Got ${movies.length} random movies. Creating voting`, { guildId: event.guildId, requestId }) logger.debug(`Movies: ${JSON.stringify(movies.map(movie => movie.name))}`, { guildId: event.guildId, requestId }) - const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(event.guildId) + const announcementChannel: Maybe = client.getAnnouncementChannelForGuild(event.guildId) + if(!announcementChannel) { + logger.error("Could not find announcement channel. Aborting", { guildId: event.guildId, requestId }) + return + } logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId }) if(!event.scheduledStartAt) { @@ -40,7 +45,7 @@ export async function execute(event: GuildScheduledEvent) { let message = `[Abstimmung]\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty am ${date} um ${time}}! 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].name!).concat("\n") + message = message.concat(Emotes[i]).concat(": ").concat(movies[i].name ?? "Film hatte keinen Namen :(").concat("\n") } const options: MessageCreateOptions = { @@ -72,7 +77,7 @@ async function checkForPollsToClose(event: GuildScheduledEvent): Promise { //refetch event in case the time changed or the poll is already closed const events = (await event.guild.scheduledEvents.fetch()) .filter(event => event.name.toLowerCase().includes("voting offen")) - .map((value, _) => value) + .map((value) => value) if (!events || events.length <= 0) { logger.info("Did not find any events. Cancelling", { guildId: event.guildId, requestId }) diff --git a/server/events/guildScheduledEventUpdate.ts b/server/events/guildScheduledEventUpdate.ts index f183740..b3cd8b1 100644 --- a/server/events/guildScheduledEventUpdate.ts +++ b/server/events/guildScheduledEventUpdate.ts @@ -3,7 +3,6 @@ import { v4 as uuid } from "uuid"; import { client, jellyfinHandler } from "../.."; import { getGuildSpecificTriggerRoleId } from "../helper/roleFilter"; import { logger } from "../logger"; -import { manageAnnouncementRoles } from "../commands/announce"; export const name = 'guildScheduledEventUpdate' @@ -11,24 +10,33 @@ export const name = 'guildScheduledEventUpdate' export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) { try { const requestId = uuid() - logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`,{guildId: newEvent.guildId, requestId}) + logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`, { guildId: newEvent.guildId, requestId }) + if (!newEvent.guild) { + logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId }) + return + } if (newEvent.description?.toLowerCase().includes("!wp") && [GuildScheduledEventStatus.Active, GuildScheduledEventStatus.Completed].includes(newEvent.status)) { - const roles = getGuildSpecificTriggerRoleId(newEvent.guildId).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 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)) + 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 { - const announcements = (await client.getAnnouncementChannelForGuild(newEvent.guild!.id).messages.fetch()).filter(message => !message.pinned) + const announcementChannel = await client.getAnnouncementChannelForGuild(newEvent.guild.id) + if(!announcementChannel) { + logger.error("Could not find announcement channel. Aborting", { guildId: newEvent.guild.id, requestId }) + return + } + const announcements = (await announcementChannel.messages.fetch()).filter(message => !message.pinned) announcements.forEach(message => message.delete()) 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.`)) diff --git a/server/events/ready.ts b/server/events/ready.ts deleted file mode 100644 index 91cef65..0000000 --- a/server/events/ready.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const name = 'ready' -export function execute(client: any) { - //console.log(`Processing ready: ${JSON.stringify(client)} has been created.`) -} diff --git a/server/events/voiceStateUpdate.ts b/server/events/voiceStateUpdate.ts index d4ef660..ac4ecee 100644 --- a/server/events/voiceStateUpdate.ts +++ b/server/events/voiceStateUpdate.ts @@ -18,8 +18,8 @@ export async function execute(oldState: VoiceState, newState: VoiceState) { } const scheduledEvents = (await newState.guild.scheduledEvents.fetch()) - .filter((key, value) => key.description?.toLowerCase().includes("!wp") && key.isActive()) - .map((key, value) => key) + .filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive()) + .map((key) => key) const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({withMember: true})))) diff --git a/server/helper/roleFilter.ts b/server/helper/roleFilter.ts index f36f577..2779737 100644 --- a/server/helper/roleFilter.ts +++ b/server/helper/roleFilter.ts @@ -16,7 +16,7 @@ export function filterRolesFromMemberUpdate(oldMember: GuildMember, newMember: G return { addedRoles, removedRoles } } -export function getGuildSpecificTriggerRoleId(guildId: string): Collection { +export function getGuildSpecificTriggerRoleId(): Collection { const outVal = new Collection() outVal.set(config.bot.watcher_role, "VIEWER") outVal.set(config.bot.jf_admin_role, "ADMIN") diff --git a/server/jellyfin/handler.ts b/server/jellyfin/handler.ts index 27e26ab..7b777d2 100644 --- a/server/jellyfin/handler.ts +++ b/server/jellyfin/handler.ts @@ -2,7 +2,7 @@ import { GuildMember } from "discord.js"; import { Config } from "../configuration"; import { Maybe, PermissionLevel } from "../interfaces"; import { logger } from "../logger"; -import { CreateUserByNameOperationRequest, DeleteUserRequest, GetItemsRequest, GetMovieRemoteSearchResultsOperationRequest, ItemLookupApi, ItemsApi, LibraryApi, SystemApi, UpdateUserPasswordOperationRequest, UpdateUserPolicyOperationRequest, UserApi } from "./apis"; +import { CreateUserByNameOperationRequest, DeleteUserRequest, GetItemsRequest, ItemsApi, SystemApi, UpdateUserPasswordOperationRequest, UpdateUserPolicyOperationRequest, UserApi } from "./apis"; import { BaseItemDto, UpdateUserPasswordRequest } from "./models"; import { UserDto } from "./models/UserDto"; import { Configuration, ConfigurationParameters } from "./runtime"; @@ -56,10 +56,6 @@ export class JellyfinHandler { return `${discordUser.displayName}${level == "TEMPORARY" ? "_tmp" : ""}` } - public async addPermissionsToUserAccount(jfUserAccount: UserDto, guildId: string, requestId: string): Promise { - throw new Error("Method not implemented."); - } - private generatePasswordForUser(): string { return (Math.random() * 10000 + 10000).toFixed(0) } @@ -253,7 +249,7 @@ export class JellyfinHandler { } const movies: BaseItemDto[] = [] for (let i = 0; i < count; i++) { - const index = Math.random() * allMovies.length + const index = Math.floor(Math.random() * allMovies.length) movies.push(...allMovies.splice(index, 1)) // maybe out of bounds? ? } diff --git a/server/structures/client.ts b/server/structures/client.ts index ce37605..9ac2c1a 100644 --- a/server/structures/client.ts +++ b/server/structures/client.ts @@ -1,13 +1,13 @@ -import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, GatewayIntentBits, Guild, IntentsBitField, Snowflake, TextChannel } from "discord.js"; -import { CommandType } from "../types/commandTypes"; -import fs from 'fs' -import { config } from "../configuration"; -import { logger } from "../logger"; -import { JellyfinHandler } from "../jellyfin/handler"; +import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, Guild, IntentsBitField, Snowflake, TextChannel } from "discord.js"; +import fs from 'fs'; import { ScheduledTask, schedule } from "node-cron"; +import { v4 as uuid } from 'uuid'; import { manageAnnouncementRoles } from "../commands/announce"; -import { v4 as uuid } from 'uuid' -import { task } from "../events/guildScheduledEventCreate"; +import { config } from "../configuration"; +import { Maybe } from "../interfaces"; +import { JellyfinHandler } from "../jellyfin/handler"; +import { logger } from "../logger"; +import { CommandType } from "../types/commandTypes"; @@ -82,8 +82,8 @@ export class ExtendedClient extends Client { private async cacheAnnouncementServer(guilds: Collection) { for (const guild of guilds.values()) { const channels: TextChannel[] = (await guild.channels.fetch()) - ?.filter(channel => channel!.id === config.bot.announcement_channel_id) - .map((value, _) => value) + ?.filter(channel => channel?.id === config.bot.announcement_channel_id) + .map((value) => value) if (!channels || channels.length != 1) { logger.error(`Could not find announcement channel for guild ${guild.name} with guildId ${guild.id}. Found ${channels}`) @@ -93,8 +93,8 @@ export class ExtendedClient extends Client { this.announcementChannels.set(guild.id, channels[0]) } } - public getAnnouncementChannelForGuild(guildId: string): TextChannel { - return this.announcementChannels.get(guildId)! //we set the channel by ourselves only if we find one, I think this is sage (mark my words) + public getAnnouncementChannelForGuild(guildId: string): Maybe { + return this.announcementChannels.get(guildId) } public async cacheUsers(guilds: Collection) { guilds.forEach((guild: Guild, id: Snowflake) => { @@ -127,18 +127,28 @@ export class ExtendedClient extends Client { public async startAnnouncementRoleBackgroundTask(guilds: Collection) { for (const guild of guilds.values()) { logger.info("Starting background task for announcement role", { guildId: guild.id }) - const textChannel: TextChannel = this.getAnnouncementChannelForGuild(guild.id) + const textChannel: Maybe = this.getAnnouncementChannelForGuild(guild.id) + if(!textChannel) { + logger.error("Could not find announcement channel. Aborting", { guildId: guild.id }) + return + } this.announcementRoleHandlerTask.set(guild.id, schedule("*/10 * * * * *", async () => { const requestId = uuid() const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]")) if (messages.size > 1) { logger.error("More than one pinned announcement Messages found. Unable to know which one people react to. Please fix!", { guildId: guild.id, requestId }) + return } else if (messages.size == 0) { logger.error("Could not find any pinned announcement messages. Unable to manage roles!", { guildId: guild.id, requestId }) + return } - const message = await messages.at(0)!.fetch() + const message = await messages.at(0)?.fetch() + if (!message) { + logger.error(`No pinned message found`, { guildId: guild.id, requestId }) + return + } //logger.debug(`Message: ${JSON.stringify(message, null, 2)}`, { guildId: guild.id, requestId }) const reactions = message.reactions.resolve("🎫") @@ -152,9 +162,8 @@ export class ExtendedClient extends Client { } } - public stopAnnouncementRoleBackgroundTask(guild: string | Guild, requestId: string) { - const guildId: string = guild instanceof Guild ? guild.id : guild - const task: ScheduledTask | undefined = guild instanceof Guild ? this.announcementRoleHandlerTask.get(guild.id) : this.announcementRoleHandlerTask.get(guild) + public stopAnnouncementRoleBackgroundTask(guildId: string, requestId: string) { + const task: Maybe = this.announcementRoleHandlerTask.get(guildId) if (!task) { logger.error(`No task found for guildID ${guildId}.`, { guildId, requestId }) return From d22e38efbf6968c42b4d819c3b7b79b3f939c244 Mon Sep 17 00:00:00 2001 From: Sammy Date: Thu, 15 Jun 2023 22:02:37 +0200 Subject: [PATCH 11/11] fix build --- server/events/guildMemberUpdate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/events/guildMemberUpdate.ts b/server/events/guildMemberUpdate.ts index 4b0d773..6de7cf2 100644 --- a/server/events/guildMemberUpdate.ts +++ b/server/events/guildMemberUpdate.ts @@ -9,7 +9,7 @@ export async function execute(oldMember: GuildMember, newMember: GuildMember) { try { const requestId = uuid() const changedRoles: ChangedRoles = filterRolesFromMemberUpdate(oldMember, newMember) - const triggerRoleIds: Collection = getGuildSpecificTriggerRoleId(oldMember.guild.id) + const triggerRoleIds: Collection = getGuildSpecificTriggerRoleId() triggerRoleIds.forEach((level, key) => { const addedRoleMatches = changedRoles.addedRoles.find(aRole => aRole.id === key)