From d6300e8bec85d6791bb9159dc442ea04af6e6d7b Mon Sep 17 00:00:00 2001 From: Sammy Date: Fri, 16 Jun 2023 20:15:36 +0200 Subject: [PATCH 1/4] improve movie name resolving When creating the poll the bot will now only request movie names instead of movies This improves the errorhandling because the movie names cannot be null Also the movieName function filters empty movienames and will guarantee the requested number of names --- server/events/guildScheduledEventCreate.ts | 6 +++--- server/jellyfin/handler.ts | 13 ++++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/server/events/guildScheduledEventCreate.ts b/server/events/guildScheduledEventCreate.ts index d14721e..7a28fee 100644 --- a/server/events/guildScheduledEventCreate.ts +++ b/server/events/guildScheduledEventCreate.ts @@ -24,10 +24,10 @@ export async function execute(event: GuildScheduledEvent) { 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.getRandomMovies(5, event.guildId, requestId) + const movies = await yavinJellyfinHandler.getRandomMovieNames(5, event.guildId, requestId) 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 }) + logger.debug(`Movies: ${JSON.stringify(movies)}`, { guildId: event.guildId, requestId }) const announcementChannel: Maybe = client.getAnnouncementChannelForGuild(event.guildId) if(!announcementChannel) { @@ -45,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 ?? "Film hatte keinen Namen :(").concat("\n") + message = message.concat(Emotes[i]).concat(": ").concat(movies[i]).concat("\n") } const options: MessageCreateOptions = { diff --git a/server/jellyfin/handler.ts b/server/jellyfin/handler.ts index a1af178..e47ac6c 100644 --- a/server/jellyfin/handler.ts +++ b/server/jellyfin/handler.ts @@ -242,10 +242,21 @@ export class JellyfinHandler { const index = Math.floor(Math.random() * allMovies.length) movies.push(...allMovies.splice(index, 1)) // maybe out of bounds? ? } - return movies } + public async getRandomMovieNames(count: number, guildId: string, requestId: string): Promise { + logger.info(`${count} random movie names requested`, { guildId, requestId }) + + let movieCount = 0 + let movieNames: string[] + do { + movieNames = (await this.getRandomMovies(count, guildId, requestId)).filter(movie => movie.name && movie.name.length > 0).map(movie => movie.name) + movieCount = movieNames.length + } while (movieCount < count) + return movieNames + } + } export enum UserUpsertResult { enabled, created } -- 2.45.2 From 07849d331acd3985248af91282e2e6ad21618277 Mon Sep 17 00:00:00 2001 From: Sammy Date: Sat, 17 Jun 2023 12:00:14 +0200 Subject: [PATCH 2/4] move scheduling of pollclose task to startup Also moved check function to closepoll.ts --- server/commands/closepoll.ts | 32 ++++++++++++++- server/events/guildScheduledEventCreate.ts | 45 +--------------------- server/events/guildScheduledEventUpdate.ts | 1 + server/structures/client.ts | 13 ++++++- 4 files changed, 44 insertions(+), 47 deletions(-) diff --git a/server/commands/closepoll.ts b/server/commands/closepoll.ts index b254f38..1de84bb 100644 --- a/server/commands/closepoll.ts +++ b/server/commands/closepoll.ts @@ -1,13 +1,13 @@ +import { addDays, format, isAfter, toDate } from 'date-fns' 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 { Maybe } from '../interfaces' 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', @@ -145,4 +145,32 @@ function extractMovieFromMessageByEmote(message: Message, emote: string): string const movie = emoteLines[0].substring(emoteLines[0].indexOf(emote) + emote.length + 2) // plus colon and space return movie +} + +export async function checkForPollsToClose(guild: Guild): Promise { + const requestId = uuid() + logger.info(`Automatic check for poll closing.`, { guildId: guild.id, requestId }) + const events = (await guild.scheduledEvents.fetch()).filter(event => event.name.toLocaleLowerCase().includes("voting offen")).map(event => event) + if(events.length > 1) { + logger.error("Handling more than one Event is not implemented yet. Found more than one poll to close") + return + } else if(events.length == 0) { + logger.info("Could not find any events. Cancelling", { guildId: guild.id, requestId }) + } + + const updatedEvent = events[0] //add two hours because of different timezones in discord api and Date.now() + if (!updatedEvent.scheduledStartTimestamp) { + logger.error("Event does not have a scheduled start time. Cancelling", { guildId: guild.id, requestId }) + return + } + + const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp) + const closePollDate: Date = addDays(eventDate, -2) + + if (isAfter(Date.now(), closePollDate)) { + logger.info("Less than two days until event. Closing poll", { guildId: guild.id, requestId }) + closePoll(guild, requestId) + } else { + logger.info(`ScheduledStart: ${closePollDate}. Now: ${toDate(Date.now())}`, { guildId: guild.id, requestId }) + } } \ No newline at end of file diff --git a/server/events/guildScheduledEventCreate.ts b/server/events/guildScheduledEventCreate.ts index 7a28fee..985bb5c 100644 --- a/server/events/guildScheduledEventCreate.ts +++ b/server/events/guildScheduledEventCreate.ts @@ -1,10 +1,8 @@ -import { addDays, format, isAfter } from "date-fns"; -import toDate from "date-fns/fp/toDate"; +import { format } from "date-fns"; import { GuildScheduledEvent, Message, MessageCreateOptions, TextChannel } from "discord.js"; -import { ScheduledTask, schedule } from "node-cron"; +import { ScheduledTask } from "node-cron"; import { v4 as uuid } from "uuid"; import { client, yavinJellyfinHandler } from "../.."; -import { closePoll } from "../commands/closepoll"; import { config } from "../configuration"; import { Maybe } from "../interfaces"; import { logger } from "../logger"; @@ -59,46 +57,7 @@ export async function execute(event: GuildScheduledEvent) { sentMessage.react(Emotes[i]) } - if (!task) { - task = schedule("0 * * * * *", () => checkForPollsToClose(event)) - } - // sentMessage.pin() //todo: uncomment when bot has permission to pin messages. Also update closepoll.ts to only fetch pinned messages } } -async function checkForPollsToClose(event: GuildScheduledEvent): Promise { - const requestId = uuid() - logger.info(`Automatic check for poll closing.`, { guildId: event.guildId, requestId }) - if (!event.guild) { - logger.error("No guild in event. Cancelling.", { guildId: event.guildId, requestId }) - return - } - //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) - - if (!events || events.length <= 0) { - logger.info("Did not find any events. Cancelling", { guildId: event.guildId, requestId }) - return - } else if (events.length > 1) { - logger.error(`More than one event found. Don't know which one is the right one :( Events: ${JSON.stringify(events, null, 2)}`, { guildId: event.guildId, requestId }) - return - } - const updatedEvent = events[0] //add two hours because of different timezones in discord api and Date.now() - if (!updatedEvent.scheduledStartTimestamp) { - logger.error("Event does not have a scheduled start time. Cancelling", { guildId: event.guildId, requestId }) - return - } - - const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp) - const closePollDate: Date = addDays(eventDate, -2) - - if (isAfter(Date.now(), closePollDate)) { - logger.info("Less than two days until event. Closing poll", { guildId: event.guildId, requestId }) - closePoll(event.guild, requestId) - } else { - logger.info(`ScheduledStart: ${closePollDate}. Now: ${toDate(Date.now())}`, { guildId: event.guildId, requestId }) - } -} \ No newline at end of file diff --git a/server/events/guildScheduledEventUpdate.ts b/server/events/guildScheduledEventUpdate.ts index b3cd8b1..eed87e7 100644 --- a/server/events/guildScheduledEventUpdate.ts +++ b/server/events/guildScheduledEventUpdate.ts @@ -27,6 +27,7 @@ export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildSche if (!members.find(x => x.id == member.id)) members.push(member) } + if (newEvent.status === GuildScheduledEventStatus.Active) createJFUsers(members, newEvent.name, requestId) diff --git a/server/structures/client.ts b/server/structures/client.ts index 6ecaf6b..6d74663 100644 --- a/server/structures/client.ts +++ b/server/structures/client.ts @@ -8,6 +8,7 @@ import { Maybe } from "../interfaces"; import { JellyfinHandler } from "../jellyfin/handler"; import { logger } from "../logger"; import { CommandType } from "../types/commandTypes"; +import { checkForPollsToClose } from "../commands/closepoll"; @@ -16,8 +17,9 @@ export class ExtendedClient extends Client { private commandFilePath = `${__dirname}/../commands` 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 + private announcementChannels: Collection = new Collection() //guildId to TextChannel + private announcementRoleHandlerTask: Collection = new Collection() //one task per guild + private pollCloseBackgroundTasks: Collection = new Collection() public constructor(jf: JellyfinHandler) { const intents: IntentsBitField = new IntentsBitField() intents.add(IntentsBitField.Flags.GuildMembers, IntentsBitField.Flags.MessageContent, IntentsBitField.Flags.Guilds, IntentsBitField.Flags.DirectMessages, IntentsBitField.Flags.GuildScheduledEvents, IntentsBitField.Flags.GuildVoiceStates) @@ -73,6 +75,7 @@ export class ExtendedClient extends Client { this.cacheUsers(guilds) await this.cacheAnnouncementServer(guilds) this.startAnnouncementRoleBackgroundTask(guilds) + this.startPollCloceBackgroundTasks() }) } catch (error) { logger.info(`Error refreshing slash commands: ${error}`) @@ -169,4 +172,10 @@ export class ExtendedClient extends Client { } task.stop() } + + private async startPollCloceBackgroundTasks() { + for(const guild of this.guilds.cache) { + this.pollCloseBackgroundTasks.set(guild[1].id, schedule("0 * * * * *", () => checkForPollsToClose(guild[1]))) + } + } } -- 2.45.2 From 4cc332820fa06c25bc4c2fea5ba1b138462eaf23 Mon Sep 17 00:00:00 2001 From: Sammy Date: Sat, 17 Jun 2023 13:03:48 +0200 Subject: [PATCH 3/4] prevent poll close if event is less than 24h old --- server/commands/closepoll.ts | 8 ++++++++ server/structures/client.ts | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/server/commands/closepoll.ts b/server/commands/closepoll.ts index 1de84bb..88c5f90 100644 --- a/server/commands/closepoll.ts +++ b/server/commands/closepoll.ts @@ -164,6 +164,14 @@ export async function checkForPollsToClose(guild: Guild): Promise { return } + const createDate: Date = toDate(updatedEvent.createdTimestamp) + const closePollMinDate: Date = addDays(createDate, 1) + + if(isAfter(closePollMinDate, Date.now())) { + logger.info("Event is less than 24h old. Not closing poll!", { guildId: guild.id, requestId }) + return + } + const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp) const closePollDate: Date = addDays(eventDate, -2) diff --git a/server/structures/client.ts b/server/structures/client.ts index 6d74663..fae18b3 100644 --- a/server/structures/client.ts +++ b/server/structures/client.ts @@ -75,7 +75,7 @@ export class ExtendedClient extends Client { this.cacheUsers(guilds) await this.cacheAnnouncementServer(guilds) this.startAnnouncementRoleBackgroundTask(guilds) - this.startPollCloceBackgroundTasks() + this.startPollCloseBackgroundTasks() }) } catch (error) { logger.info(`Error refreshing slash commands: ${error}`) @@ -173,7 +173,7 @@ export class ExtendedClient extends Client { task.stop() } - private async startPollCloceBackgroundTasks() { + private async startPollCloseBackgroundTasks() { for(const guild of this.guilds.cache) { this.pollCloseBackgroundTasks.set(guild[1].id, schedule("0 * * * * *", () => checkForPollsToClose(guild[1]))) } -- 2.45.2 From 670a64af22ce6b22ec458cd69c912aea0e920cf9 Mon Sep 17 00:00:00 2001 From: Sammy Date: Sat, 17 Jun 2023 13:18:52 +0200 Subject: [PATCH 4/4] Check if less than 2 days between create and start for deciding if to close --- server/commands/closepoll.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/server/commands/closepoll.ts b/server/commands/closepoll.ts index 88c5f90..78a2a0b 100644 --- a/server/commands/closepoll.ts +++ b/server/commands/closepoll.ts @@ -1,4 +1,4 @@ -import { addDays, format, isAfter, toDate } from 'date-fns' +import { addDays, differenceInDays, format, isAfter, toDate } from 'date-fns' import { Guild, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, TextChannel } from 'discord.js' import { v4 as uuid } from 'uuid' import { client } from '../..' @@ -23,7 +23,7 @@ export default new Command({ } const guildId = command.guildId logger.info("Got command for closing poll!", { guildId, requestId }) - + command.followUp("Alles klar, beende die Umfrage :)") closePoll(command.guild, requestId) } @@ -34,7 +34,7 @@ export async function closePoll(guild: Guild, requestId: string) { logger.info("stopping poll", { guildId, requestId }) const announcementChannel: Maybe = client.getAnnouncementChannelForGuild(guildId) - if(!announcementChannel) { + if (!announcementChannel) { logger.error("Could not find the textchannel. Unable to close poll.", { guildId, requestId }) return } @@ -55,16 +55,16 @@ 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 }) - + logger.info("Deleting vote message") await lastMessage.delete() const event = await getEvent(guild, guild.id, requestId) - if(event) { + if (event) { updateEvent(event, votes, guild, guildId, requestId) sendVoteClosedMessage(event, votes[0].movie, guildId, requestId) } @@ -82,7 +82,7 @@ async function sendVoteClosedMessage(event: GuildScheduledEvent, movie: string, } const announcementChannel = client.getAnnouncementChannelForGuild(guildId) logger.info("Sending vote closed message.", { guildId, requestId }) - if(!announcementChannel) { + if (!announcementChannel) { logger.error("Could not find announcement channel. Please fix!", { guildId, requestId }) return } @@ -151,10 +151,10 @@ export async function checkForPollsToClose(guild: Guild): Promise { const requestId = uuid() logger.info(`Automatic check for poll closing.`, { guildId: guild.id, requestId }) const events = (await guild.scheduledEvents.fetch()).filter(event => event.name.toLocaleLowerCase().includes("voting offen")).map(event => event) - if(events.length > 1) { + if (events.length > 1) { logger.error("Handling more than one Event is not implemented yet. Found more than one poll to close") return - } else if(events.length == 0) { + } else if (events.length == 0) { logger.info("Could not find any events. Cancelling", { guildId: guild.id, requestId }) } @@ -165,14 +165,14 @@ export async function checkForPollsToClose(guild: Guild): Promise { } const createDate: Date = toDate(updatedEvent.createdTimestamp) - const closePollMinDate: Date = addDays(createDate, 1) + const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp) + const difference: number = differenceInDays(createDate, eventDate) - if(isAfter(closePollMinDate, Date.now())) { - logger.info("Event is less than 24h old. Not closing poll!", { guildId: guild.id, requestId }) + if (difference <= 2) { + logger.info("Less than two days between event create and event start. Not closing poll.", { guildId: guild.id, requestId }) return } - - const eventDate: Date = toDate(updatedEvent.scheduledStartTimestamp) + const closePollDate: Date = addDays(eventDate, -2) if (isAfter(Date.now(), closePollDate)) { -- 2.45.2