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