From 2707f7d73bc2caae477a5cb7e1b142ad055d4835 Mon Sep 17 00:00:00 2001 From: Sammy Date: Sat, 10 Jun 2023 14:23:10 +0200 Subject: [PATCH] Add automatic creation of vote message with random movies When a !nextwp event is created the bot will fetch random movies and create a message that people can vote on --- server/configuration.ts | 5 ++- server/events/guildScheduledEventCreate.ts | 44 +++++++++++++++++++ server/events/guildScheduledEventUpdate.ts | 3 +- server/jellyfin/handler.ts | 50 +++++++++++++++++++--- 4 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 server/events/guildScheduledEventCreate.ts diff --git a/server/configuration.ts b/server/configuration.ts index fa3d020..adb58fc 100644 --- a/server/configuration.ts +++ b/server/configuration.ts @@ -1,4 +1,5 @@ import dotenv from "dotenv" +import { AddListingProviderRequestToJSON } from "./jellyfin" dotenv.config() interface options { @@ -22,6 +23,7 @@ export interface Config { workaround_token: string watcher_role: string jf_admin_role: string + announcement_channel_id: string } } export const config: Config = { @@ -53,6 +55,7 @@ export const config: Config = { jellyfin_url: process.env.JELLYFIN_URL ?? "", workaround_token: process.env.TOKEN ?? "", watcher_role: process.env.WATCHER_ROLE ?? "", - jf_admin_role: process.env.ADMIN_ROLE ?? "" + jf_admin_role: process.env.ADMIN_ROLE ?? "", + announcement_channel_id: process.env.CHANNEL_ID ?? "" } } diff --git a/server/events/guildScheduledEventCreate.ts b/server/events/guildScheduledEventCreate.ts new file mode 100644 index 0000000..1896691 --- /dev/null +++ b/server/events/guildScheduledEventCreate.ts @@ -0,0 +1,44 @@ +import { BaseFetchOptions, ChannelType, GuildBasedChannel, GuildChannelResolvable, GuildMember, GuildScheduledEvent, GuildScheduledEventStatus, Message, TextChannel, messageLink } from "discord.js"; +import { v4 as uuid } from "uuid"; +import { jellyfinHandler } from "../.."; +import { getGuildSpecificTriggerRoleId } from "../helper/roleFilter"; +import { logger } from "../logger"; +import { config } from "../configuration"; + + +export const name = 'guildScheduledEventCreate' + +const emotes = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"] + +export async function execute(event: GuildScheduledEvent) { + const requestId = uuid() + logger.debug(`New event created: ${JSON.stringify(event, null, 2)}`, { guildId: event.guildId, requestId }) + + if (event.name.toLowerCase().includes("!nextwp")) { + logger.info("Event was a placeholder event to start a new watchparty and voting. Creating vote!", { guildId: event.guildId, requestId }) + logger.debug("Renaming event", { guildId: event.guildId, requestId }) + event.edit({ name: "Watchparty - Voting offen" }) + const movies = await jellyfinHandler.getRandomMovies(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 }) + + + + const channel: TextChannel = (await event.guild?.channels.fetch())?.filter(channel => channel!.id === config.bot.announcement_channel_id).map((value, _) => value)[0] //todo: needs to be done less sketchy + logger.debug(`Found channel ${JSON.stringify(channel, null, 2)}`) + + let message = "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 channel.fetch()).send(message) + + for (let i = 0; i < movies.length; i++) { + sentMessage.react(emotes[i]) + } + + } +} \ No newline at end of file diff --git a/server/events/guildScheduledEventUpdate.ts b/server/events/guildScheduledEventUpdate.ts index c08353f..d731b09 100644 --- a/server/events/guildScheduledEventUpdate.ts +++ b/server/events/guildScheduledEventUpdate.ts @@ -9,8 +9,8 @@ export const name = 'guildScheduledEventUpdate' export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) { try { - logger.info(JSON.stringify(newEvent, null, 2)) const requestId = uuid() + logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`,{guildId: newEvent.guildId, requestId}) if (newEvent.description?.toLowerCase().includes("!wp") && [GuildScheduledEventStatus.Active, GuildScheduledEventStatus.Completed].includes(newEvent.status)) { const roles = getGuildSpecificTriggerRoleId(newEvent.guildId).map((key, value)=> value) @@ -37,6 +37,7 @@ export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildSche logger.error(error) } } + async function createJFUsers(members: GuildMember[], movieName: string, requestId?: string) { logger.info(`Creating users for: \n ${JSON.stringify(members, null, 2)}`) members.forEach(member => { diff --git a/server/jellyfin/handler.ts b/server/jellyfin/handler.ts index 18bb0e4..7bdd0df 100644 --- a/server/jellyfin/handler.ts +++ b/server/jellyfin/handler.ts @@ -2,8 +2,8 @@ import { GuildMember } from "discord.js"; import { Config } from "../configuration"; import { Maybe, PermissionLevel } from "../interfaces"; import { logger } from "../logger"; -import { CreateUserByNameOperationRequest, DeleteUserRequest, SystemApi, UpdateUserPasswordOperationRequest, UpdateUserPolicyOperationRequest, UserApi } from "./apis"; -import { UpdateUserPasswordRequest } from "./models"; +import { CreateUserByNameOperationRequest, DeleteUserRequest, GetItemsRequest, GetMovieRemoteSearchResultsOperationRequest, ItemLookupApi, ItemsApi, LibraryApi, SystemApi, UpdateUserPasswordOperationRequest, UpdateUserPolicyOperationRequest, UserApi } from "./apis"; +import { BaseItemDto, UpdateUserPasswordRequest } from "./models"; import { UserDto } from "./models/UserDto"; import { Configuration, ConfigurationParameters } from "./runtime"; @@ -12,6 +12,7 @@ export class JellyfinHandler { private userApi: UserApi private systemApi: SystemApi + private moviesApi: ItemsApi private token: string private authHeader: { headers: { 'X-Emby-Authorization': string } } private config: Config @@ -24,7 +25,7 @@ export class JellyfinHandler { } return this.serverName } - constructor(_config: Config, _userApi?: UserApi, _systemApi?: SystemApi) { + constructor(_config: Config, _userApi?: UserApi, _systemApi?: SystemApi, _itemsApi?: ItemsApi) { this.config = _config this.token = this.config.bot.jellfin_token this.authHeader = { @@ -40,8 +41,14 @@ export class JellyfinHandler { basePath: this.config.bot.jellyfin_url, headers: this.authHeader.headers } + const libraryApiConfigurationParams: ConfigurationParameters = { + basePath: this.config.bot.jellyfin_url, + headers: this.authHeader.headers + } + this.userApi = _userApi ?? new UserApi(new Configuration(userApiConfigurationParams)) this.systemApi = _systemApi ?? new SystemApi(new Configuration(systemApiConfigurationParams)) + this.moviesApi = _itemsApi ?? new ItemsApi(new Configuration(libraryApiConfigurationParams)) logger.info(`Initialized Jellyfin handler`, { requestId: 'Init' }) } @@ -222,7 +229,38 @@ export class JellyfinHandler { } } - -} -export enum UserUpsertResult {enabled, created} + public async getAllMovies(guildId: string, requestId: string): Promise { + logger.info("requesting all movies from jellyfin", { guildId: guildId, requestId }) + + const liloJfUser = await this.getUser({ guild: { id: guildId }, displayName: "lilo" }, requestId) + + const searchParams: GetItemsRequest = { + userId: liloJfUser?.id, + parentId: "f137a2dd21bbc1b99aa5c0f6bf02a805" // collection ID for all movies + } + const movies = (await (this.moviesApi.getItems(searchParams))).items?.filter(item => !item.isFolder) + // logger.debug(JSON.stringify(movies, null, 2), { guildId: guildId, requestId }) + logger.info(`Found ${movies?.length} movies in total`, { guildId: guildId, requestId }) + return movies ?? [] + } + + public async getRandomMovies(count: number, guildId: string, requestId: string): Promise { + logger.info(`${count} random movies requested.`, { guildId: guildId, requestId }) + const allMovies = await this.getAllMovies(guildId, requestId) + if (count >= allMovies.length) { + logger.info(`${count} random movies requested but found only ${allMovies.length}. Returning all Movies.`) + return allMovies + } + const movies: BaseItemDto[] = [] + for (let i = 0; i < count; i++) { + const index = Math.random() * allMovies.length + movies.push(...allMovies.splice(index, 1)) // maybe out of bounds? ? + } + + return movies + } + + +} +export enum UserUpsertResult { enabled, created }