jellyfin-discord-bot/server/jellyfin/handler.ts
Sammy d6300e8bec
All checks were successful
Build a docker image for node-jellyfin-role-bot / build-docker-image (push) Successful in 1m55s
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
2023-06-16 20:15:36 +02:00

264 lines
11 KiB
TypeScript

import { GuildMember } from "discord.js";
import { JellyfinConfig, Maybe, PermissionLevel } from "../interfaces";
import { logger } from "../logger";
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";
export class JellyfinHandler {
private userApi: UserApi
private systemApi: SystemApi
private moviesApi: ItemsApi
private token: string
private authHeader: { headers: { 'X-Emby-Authorization': string } }
private config: JellyfinConfig
private serverName = "";
constructor(_config: JellyfinConfig, _userApi?: UserApi, _systemApi?: SystemApi, _itemsApi?: ItemsApi) {
this.config = _config
this.token = this.config.jellyfinToken
this.authHeader = {
headers: {
"X-Emby-Authorization": this.config.jellyfinToken
}
}
const userApiConfigurationParams: ConfigurationParameters = {
basePath: this.config.jellyfinUrl,
headers: this.authHeader.headers
}
const systemApiConfigurationParams: ConfigurationParameters = {
basePath: this.config.jellyfinUrl,
headers: this.authHeader.headers
}
const libraryApiConfigurationParams: ConfigurationParameters = {
basePath: this.config.jellyfinUrl,
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' })
}
private generateJFUserName(discordUser: GuildMember, level: PermissionLevel): string {
return `${discordUser.displayName}${level == "TEMPORARY" ? "_tmp" : ""}`
}
private generatePasswordForUser(): string {
return (Math.random() * 10000 + 10000).toFixed(0)
}
public async createUserAccountForDiscordUser(discordUser: GuildMember, level: PermissionLevel, guildId?: string, requestId?: string): Promise<UserDto> {
const newUserName = this.generateJFUserName(discordUser, level)
logger.info(`New Username for ${discordUser.displayName}: ${newUserName}`, { guildId, requestId })
const req: CreateUserByNameOperationRequest = {
createUserByNameRequest: {
name: newUserName,
password: this.generatePasswordForUser(),
}
}
logger.debug(JSON.stringify(req), { requestId, guildId })
const createResult = await this.userApi.createUserByName(req)
if (createResult) {
(await discordUser.createDM()).send(`Ich hab dir mal nen Account angelegt :)\nDein Username ist ${createResult.name}, dein Password ist "${req.createUserByNameRequest.password}"!`)
return createResult
}
else throw new Error('Could not create User in Jellyfin')
}
public async isUserAlreadyPresent(discordUser: GuildMember, requestId?: string): Promise<boolean> {
const jfuser = await this.getUser(discordUser, requestId)
logger.debug(`Presence for DiscordUser ${discordUser.id}:${jfuser !== undefined}`, { guildId: discordUser.guild.id, requestId })
return jfuser !== undefined
}
public async getCurrentUsers(guildId: string, requestId?: string): Promise<UserDto[]> {
try {
logger.info(`Fetching current users from Jellyfin`, { requestId, guildId })
const result = await this.userApi.getUsers(undefined, this.authHeader)
return result
} catch (error) {
logger.error(`Could not fetch current users from jellyfin`, { guildId, requestId })
}
return []
}
public async getUser(discordUser: GuildMember, requestId?: string): Promise<Maybe<UserDto>> {
logger.info(`Getting user for discord member ${discordUser.displayName}`, { requestId, guildId: discordUser.guild.id })
const jfUsers = await this.getCurrentUsers(discordUser.guild.id, requestId)
const foundUser = jfUsers.find(x => x.name?.includes(discordUser.displayName))
return foundUser
}
public async removeUser(newMember: GuildMember, level: PermissionLevel, requestId?: string) {
logger.info(`${level == "TEMPORARY" ? "Deleting" : "Disabling"} user ${newMember.displayName}, but method is not implemented`, { requestId, guildId: newMember.guild.id })
const jfuser = await this.getUser(newMember, requestId)
if (jfuser && jfuser.id) {
if (level === "TEMPORARY") {
const r: DeleteUserRequest = {
userId: jfuser.id
}
this.userApi.deleteUser(r)
}
else
await this.disableUser(jfuser, newMember.guild.id, requestId)
}
}
public async purge(guildId: string, requestId?: string) {
logger.info("Deleting tmp users", { requestId, guildId })
const users = (await this.userApi.getUsers()).filter(user => user.name?.endsWith("_tmp"))
users.forEach(user => {
if (user.id) {
const r: DeleteUserRequest = {
userId: user.id
}
this.userApi.deleteUser(r)
}
})
}
public async resetUserPasswort(member: GuildMember, requestId?: string) {
logger.info(`Resetting password for user ${member.displayName}`, { requestId, guildId: member.guild.id })
const jfUser = await this.getUser(member, requestId)
if (jfUser && jfUser.id) {
// const reset: UpdateUserPasswordRequest = {
// resetPassword: true
// }
// const shit: UpdateUserPasswordOperationRequest = {
// updateUserPasswordRequest: reset,
// userId: jfUser.id
// }
// logger.info(JSON.stringify(jfUser.policy, null, 2))
// logger.info("Resetting password", {requestId})
// await this.userApi.updateUserPassword(shit);
const password = this.generatePasswordForUser()
const passwordRequest: UpdateUserPasswordRequest = {
// resetPassword: true,
currentPw: "",
newPw: password
}
const passwordOperationRequest: UpdateUserPasswordOperationRequest = {
updateUserPasswordRequest: passwordRequest,
userId: jfUser.id
}
logger.info("Setting new password", { requestId, guildId: member.guild.id })
await this.userApi.updateUserPassword(passwordOperationRequest);
(await member.createDM()).send(`Hier ist dein neues Passwort: ${password}`)
} else {
(await member.createDM()).send("Ich konnte leider keinen User von dir auf Jellyfin finden. Bitte melde dich bei Markus oder Samantha!")
}
}
public async disableUser(user: UserDto, guildId?: string, requestId?: string): Promise<void> {
if (user.id) {
const jfUser = await this.getUser(<GuildMember>{ displayName: user.name, guild: { id: guildId } }, requestId)
logger.info(`Trying to disable user: ${user.name}|${user.id}|${JSON.stringify(jfUser, null, 2)}`, { guildId, requestId })
const r: UpdateUserPolicyOperationRequest = {
userId: user.id ?? "",
updateUserPolicyRequest: {
...jfUser?.policy,
isDisabled: true,
}
}
await this.userApi.updateUserPolicy(r)
logger.info(`Succeeded with disabling user: ${user.name}`, { guildId, requestId })
}
else {
logger.error(`Can not disable user ${JSON.stringify(user)}, has no id?!`, { requestId, guildId })
}
}
public async enableUser(user: UserDto, guildId: string, requestId?: string): Promise<void> {
if (user.id) {
const jfUser = await this.getUser(<GuildMember>{ displayName: user.name, guild: { id: guildId } }, requestId)
logger.info(`Trying to enable user: ${user.name}|${user.id}|${JSON.stringify(jfUser, null, 2)}`, { guildId, requestId })
const r: UpdateUserPolicyOperationRequest = {
userId: user.id ?? "",
updateUserPolicyRequest: {
...jfUser?.policy,
isDisabled: false,
}
}
await this.userApi.updateUserPolicy(r)
logger.info(`Succeeded with enabling user: ${user.name}`, { guildId, requestId })
}
else {
logger.error(`Can not enable user ${JSON.stringify(user)}, has no id?!`, { requestId, guildId })
}
}
public async upsertUser(newMember: GuildMember, level: PermissionLevel, requestId?: string): Promise<UserUpsertResult> {
logger.info(`Trying to upsert user ${newMember.displayName}, with permissionLevel ${level}`, { guildId: newMember.guild.id, requestId })
const jfuser = await this.getUser(newMember, requestId)
if (jfuser && !jfuser.policy?.isDisabled) {
logger.info(`User with name ${newMember.displayName} is already present`, { guildId: newMember.guild.id, requestId })
await this.enableUser(jfuser, newMember.guild.id, requestId)
return UserUpsertResult.enabled
} else {
this.createUserAccountForDiscordUser(newMember, level, newMember.guild.id, requestId)
return UserUpsertResult.created
}
}
public async getAllMovies(guildId: string, requestId: string): Promise<BaseItemDto[]> {
logger.info("requesting all movies from jellyfin", { guildId, requestId })
const searchParams: GetItemsRequest = {
userId: this.config.collectionUser,
parentId: this.config.movieCollectionId // 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, requestId })
return movies ?? []
}
public async getRandomMovies(count: number, guildId: string, requestId: string): Promise<BaseItemDto[]> {
logger.info(`${count} random movies requested.`, { 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.`, { guildId, requestId })
return allMovies
}
const movies: BaseItemDto[] = []
for (let i = 0; i < count; i++) {
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<string[]> {
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 => <string> movie.name)
movieCount = movieNames.length
} while (movieCount < count)
return movieNames
}
}
export enum UserUpsertResult { enabled, created }