2023-05-04 23:34:53 +02:00
import { GuildMember } from "discord.js" ;
2023-06-09 23:56:45 +02:00
import { Config } from "../configuration" ;
import { Maybe , PermissionLevel } from "../interfaces" ;
import { logger } from "../logger" ;
2023-06-15 21:56:15 +02:00
import { CreateUserByNameOperationRequest , DeleteUserRequest , GetItemsRequest , ItemsApi , SystemApi , UpdateUserPasswordOperationRequest , UpdateUserPolicyOperationRequest , UserApi } from "./apis" ;
2023-06-10 14:23:10 +02:00
import { BaseItemDto , UpdateUserPasswordRequest } from "./models" ;
2023-05-04 23:34:53 +02:00
import { UserDto } from "./models/UserDto" ;
import { Configuration , ConfigurationParameters } from "./runtime" ;
2023-06-04 01:02:29 +02:00
2023-04-16 02:04:08 +02:00
export class JellyfinHandler {
private userApi : UserApi
private systemApi : SystemApi
2023-06-10 14:23:10 +02:00
private moviesApi : ItemsApi
2023-04-16 02:04:08 +02:00
private token : string
2023-06-04 03:14:45 +02:00
private authHeader : { headers : { 'X-Emby-Authorization' : string } }
2023-04-16 02:04:08 +02:00
private config : Config
2023-04-18 23:46:26 +02:00
private serverName = "" ;
2023-04-16 02:04:08 +02:00
public async ServerName ( ) : Promise < string > {
if ( this . serverName === "" ) {
const info = await this . systemApi . getSystemInfo ( this . authHeader )
2023-05-04 23:34:53 +02:00
this . serverName = info . serverName ? ? this . config . bot . jellyfin_url
2023-04-16 02:04:08 +02:00
}
return this . serverName
}
2023-06-10 14:23:10 +02:00
constructor ( _config : Config , _userApi? : UserApi , _systemApi? : SystemApi , _itemsApi? : ItemsApi ) {
2023-04-16 02:04:08 +02:00
this . config = _config
this . token = this . config . bot . jellfin_token
this . authHeader = {
headers : {
2023-06-04 03:14:45 +02:00
"X-Emby-Authorization" : this . config . bot . workaround_token
2023-04-16 02:04:08 +02:00
}
}
2023-06-03 22:06:23 +02:00
const userApiConfigurationParams : ConfigurationParameters = {
basePath : this.config.bot.jellyfin_url ,
headers : this.authHeader.headers
}
const systemApiConfigurationParams : ConfigurationParameters = {
basePath : this.config.bot.jellyfin_url ,
headers : this.authHeader.headers
}
2023-06-10 14:23:10 +02:00
const libraryApiConfigurationParams : ConfigurationParameters = {
basePath : this.config.bot.jellyfin_url ,
headers : this.authHeader.headers
}
2023-06-03 22:06:23 +02:00
this . userApi = _userApi ? ? new UserApi ( new Configuration ( userApiConfigurationParams ) )
this . systemApi = _systemApi ? ? new SystemApi ( new Configuration ( systemApiConfigurationParams ) )
2023-06-10 14:23:10 +02:00
this . moviesApi = _itemsApi ? ? new ItemsApi ( new Configuration ( libraryApiConfigurationParams ) )
2023-05-04 23:34:53 +02:00
logger . info ( ` Initialized Jellyfin handler ` , { requestId : 'Init' } )
2023-04-16 02:04:08 +02:00
}
2023-06-04 16:35:43 +02:00
private generateJFUserName ( discordUser : GuildMember , level : PermissionLevel ) : string {
return ` ${ discordUser . displayName } ${ level == "TEMPORARY" ? "_tmp" : "" } `
2023-04-16 02:04:08 +02:00
}
2023-06-04 01:02:29 +02:00
private generatePasswordForUser ( ) : string {
return ( Math . random ( ) * 10000 + 10000 ) . toFixed ( 0 )
2023-04-20 20:54:20 +02:00
}
2023-06-04 16:35:43 +02:00
public async createUserAccountForDiscordUser ( discordUser : GuildMember , level : PermissionLevel , guildId? : string , requestId? : string ) : Promise < UserDto > {
const newUserName = this . generateJFUserName ( discordUser , level )
2023-04-18 23:46:26 +02:00
logger . info ( ` New Username for ${ discordUser . displayName } : ${ newUserName } ` , { guildId , requestId } )
2023-05-04 23:34:53 +02:00
const req : CreateUserByNameOperationRequest = {
createUserByNameRequest : {
name : newUserName ,
2023-06-04 01:02:29 +02:00
password : this.generatePasswordForUser ( ) ,
2023-05-04 23:34:53 +02:00
}
2023-04-20 20:54:20 +02:00
}
2023-06-06 23:27:07 +02:00
logger . debug ( JSON . stringify ( req ) , { requestId , guildId } )
2023-06-03 22:06:23 +02:00
const createResult = await this . userApi . createUserByName ( req )
2023-06-04 03:14:45 +02:00
if ( createResult ) {
( await discordUser . createDM ( ) ) . send ( ` Ich hab dir mal nen Account angelegt :) \ nDein Username ist ${ createResult . name } , dein Password ist " ${ req . createUserByNameRequest . password } "! ` )
2023-05-04 23:34:53 +02:00
return createResult
2023-04-20 20:54:20 +02:00
}
2023-06-04 01:02:29 +02:00
else throw new Error ( 'Could not create User in Jellyfin' )
2023-04-16 02:04:08 +02:00
}
2023-06-04 03:14:45 +02:00
2023-04-20 20:54:20 +02:00
public async isUserAlreadyPresent ( discordUser : GuildMember , requestId? : string ) : Promise < boolean > {
const jfuser = await this . getUser ( discordUser , requestId )
2023-06-06 23:27:07 +02:00
logger . debug ( ` Presence for DiscordUser ${ discordUser . id } : ${ jfuser !== undefined } ` , { guildId : discordUser.guild.id , requestId } )
2023-04-20 20:54:20 +02:00
return jfuser !== undefined
}
2023-06-04 03:14:45 +02:00
2023-04-18 23:46:26 +02:00
public async getCurrentUsers ( guildId : string , requestId? : string ) : Promise < UserDto [ ] > {
2023-04-16 02:04:08 +02:00
try {
2023-05-04 23:34:53 +02:00
logger . info ( ` Fetching current users from Jellyfin ` , { requestId , guildId } )
const result = await this . userApi . getUsers ( undefined , this . authHeader )
return result
2023-04-16 02:04:08 +02:00
} catch ( error ) {
logger . error ( ` Could not fetch current users from jellyfin ` , { guildId , requestId } )
}
return [ ]
}
2023-06-04 03:14:45 +02:00
2023-04-20 20:54:20 +02:00
public async getUser ( discordUser : GuildMember , requestId? : string ) : Promise < Maybe < UserDto > > {
2023-06-06 23:27:07 +02:00
logger . info ( ` Getting user for discord member ${ discordUser . displayName } ` , { requestId , guildId : discordUser.guild.id } )
2023-04-20 20:54:20 +02:00
const jfUsers = await this . getCurrentUsers ( discordUser . guild . id , requestId )
2023-06-04 16:35:43 +02:00
const foundUser = jfUsers . find ( x = > x . name ? . includes ( discordUser . displayName ) )
2023-04-20 20:54:20 +02:00
return foundUser
}
2023-06-04 03:14:45 +02:00
2023-06-04 16:35:43 +02:00
public async removeUser ( newMember : GuildMember , level : PermissionLevel , requestId? : string ) {
2023-06-06 23:27:07 +02:00
logger . info ( ` ${ level == "TEMPORARY" ? "Deleting" : "Disabling" } user ${ newMember . displayName } , but method is not implemented ` , { requestId , guildId : newMember.guild.id } )
2023-04-20 20:54:20 +02:00
const jfuser = await this . getUser ( newMember , requestId )
2023-06-04 16:35:43 +02:00
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 )
2023-06-04 01:02:29 +02:00
}
}
2023-06-04 16:35:43 +02:00
public async purge ( guildId : string , requestId? : string ) {
2023-06-06 23:27:07 +02:00
logger . info ( "Deleting tmp users" , { requestId , guildId } )
2023-06-04 16:35:43 +02:00
const users = ( await this . userApi . getUsers ( ) ) . filter ( user = > user . name ? . endsWith ( "_tmp" ) )
users . forEach ( user = > {
2023-06-06 23:27:07 +02:00
if ( user . id ) {
2023-06-04 16:35:43 +02:00
const r : DeleteUserRequest = {
userId : user.id
}
this . userApi . deleteUser ( r )
}
} )
}
2023-06-04 01:02:29 +02:00
public async resetUserPasswort ( member : GuildMember , requestId? : string ) {
2023-06-06 23:27:07 +02:00
logger . info ( ` Resetting password for user ${ member . displayName } ` , { requestId , guildId : member.guild.id } )
2023-06-04 01:02:29 +02:00
const jfUser = await this . getUser ( member , requestId )
if ( jfUser && jfUser . id ) {
2023-06-04 03:14:45 +02:00
// const reset: UpdateUserPasswordRequest = {
// resetPassword: true
// }
2023-06-04 16:35:43 +02:00
2023-06-04 03:14:45 +02:00
// 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);
2023-06-04 16:35:43 +02:00
2023-06-04 03:14:45 +02:00
const password = this . generatePasswordForUser ( )
const passwordRequest : UpdateUserPasswordRequest = {
// resetPassword: true,
currentPw : "" ,
newPw : password
2023-06-04 01:02:29 +02:00
}
2023-06-04 03:14:45 +02:00
const passwordOperationRequest : UpdateUserPasswordOperationRequest = {
updateUserPasswordRequest : passwordRequest ,
2023-06-04 01:02:29 +02:00
userId : jfUser.id
}
2023-06-06 23:27:07 +02:00
logger . info ( "Setting new password" , { requestId , guildId : member.guild.id } )
2023-06-04 03:14:45 +02:00
await this . userApi . updateUserPassword ( passwordOperationRequest ) ;
2023-06-04 01:02:29 +02:00
2023-06-04 03:14:45 +02:00
( await member . createDM ( ) ) . send ( ` Hier ist dein neues Passwort: ${ password } ` )
2023-06-04 01:02:29 +02:00
} else {
( await member . createDM ( ) ) . send ( "Ich konnte leider keinen User von dir auf Jellyfin finden. Bitte melde dich bei Markus oder Samantha!" )
2023-04-20 20:54:20 +02:00
}
2023-06-04 01:02:29 +02:00
2023-04-20 20:54:20 +02:00
}
2023-06-04 01:02:29 +02:00
public async disableUser ( user : UserDto , guildId? : string , requestId? : string ) : Promise < void > {
2023-04-20 20:54:20 +02:00
if ( user . id ) {
2023-06-04 03:14:45 +02:00
const jfUser = await this . getUser ( < GuildMember > { displayName : user.name , guild : { id : guildId } } , requestId )
2023-06-06 23:27:07 +02:00
logger . info ( ` Trying to disable user: ${ user . name } | ${ user . id } | ${ JSON . stringify ( jfUser , null , 2 ) } ` , { guildId , requestId } )
2023-05-04 23:34:53 +02:00
const r : UpdateUserPolicyOperationRequest = {
userId : user.id ? ? "" ,
updateUserPolicyRequest : {
2023-06-04 01:02:29 +02:00
. . . jfUser ? . policy ,
isDisabled : true ,
}
}
await this . userApi . updateUserPolicy ( r )
2023-06-06 23:27:07 +02:00
logger . info ( ` Succeeded with disabling user: ${ user . name } ` , { guildId , requestId } )
2023-06-04 01:02:29 +02:00
}
else {
logger . error ( ` Can not disable user ${ JSON . stringify ( user ) } , has no id?! ` , { requestId , guildId } )
}
}
2023-06-04 03:14:45 +02:00
2023-06-04 01:02:29 +02:00
public async enableUser ( user : UserDto , guildId : string , requestId? : string ) : Promise < void > {
if ( user . id ) {
2023-06-04 03:14:45 +02:00
const jfUser = await this . getUser ( < GuildMember > { displayName : user.name , guild : { id : guildId } } , requestId )
2023-06-06 23:27:07 +02:00
logger . info ( ` Trying to enable user: ${ user . name } | ${ user . id } | ${ JSON . stringify ( jfUser , null , 2 ) } ` , { guildId , requestId } )
2023-06-04 01:02:29 +02:00
const r : UpdateUserPolicyOperationRequest = {
userId : user.id ? ? "" ,
updateUserPolicyRequest : {
. . . jfUser ? . policy ,
isDisabled : false ,
2023-05-04 23:34:53 +02:00
}
2023-04-20 20:54:20 +02:00
}
2023-06-03 22:06:23 +02:00
await this . userApi . updateUserPolicy ( r )
2023-06-06 23:27:07 +02:00
logger . info ( ` Succeeded with enabling user: ${ user . name } ` , { guildId , requestId } )
2023-04-20 20:54:20 +02:00
}
else {
logger . error ( ` Can not enable user ${ JSON . stringify ( user ) } , has no id?! ` , { requestId , guildId } )
}
2023-04-18 23:46:26 +02:00
}
2023-04-20 20:54:20 +02:00
2023-06-09 23:56:45 +02:00
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 } )
2023-04-20 20:54:20 +02:00
const jfuser = await this . getUser ( newMember , requestId )
2023-06-09 23:56:45 +02:00
if ( jfuser && ! jfuser . policy ? . isDisabled ) {
2023-06-06 23:27:07 +02:00
logger . info ( ` User with name ${ newMember . displayName } is already present ` , { guildId : newMember.guild.id , requestId } )
2023-06-04 01:02:29 +02:00
await this . enableUser ( jfuser , newMember . guild . id , requestId )
2023-06-09 23:56:45 +02:00
return UserUpsertResult . enabled
2023-04-20 20:54:20 +02:00
} else {
2023-06-04 16:35:43 +02:00
this . createUserAccountForDiscordUser ( newMember , level , newMember . guild . id , requestId )
2023-06-09 23:56:45 +02:00
return UserUpsertResult . created
2023-04-18 23:46:26 +02:00
}
}
2023-06-09 23:56:45 +02:00
2023-06-10 14:23:10 +02:00
public async getAllMovies ( guildId : string , requestId : string ) : Promise < BaseItemDto [ ] > {
2023-06-12 20:36:05 +02:00
logger . info ( "requesting all movies from jellyfin" , { guildId , requestId } )
2023-06-10 14:23:10 +02:00
const liloJfUser = await this . getUser ( < GuildMember > { guild : { id : guildId } , displayName : "lilo" } , requestId )
const searchParams : GetItemsRequest = {
userId : liloJfUser?.id ,
2023-06-12 20:31:52 +02:00
parentId : this.config.bot.jf_collection_id // collection ID for all movies
2023-06-10 14:23:10 +02:00
}
const movies = ( await ( this . moviesApi . getItems ( searchParams ) ) ) . items ? . filter ( item = > ! item . isFolder )
// logger.debug(JSON.stringify(movies, null, 2), { guildId: guildId, requestId })
2023-06-12 20:36:05 +02:00
logger . info ( ` Found ${ movies ? . length } movies in total ` , { guildId , requestId } )
2023-06-10 14:23:10 +02:00
return movies ? ? [ ]
}
public async getRandomMovies ( count : number , guildId : string , requestId : string ) : Promise < BaseItemDto [ ] > {
2023-06-12 20:36:05 +02:00
logger . info ( ` ${ count } random movies requested. ` , { guildId , requestId } )
2023-06-10 14:23:10 +02:00
const allMovies = await this . getAllMovies ( guildId , requestId )
if ( count >= allMovies . length ) {
2023-06-12 20:36:05 +02:00
logger . info ( ` ${ count } random movies requested but found only ${ allMovies . length } . Returning all Movies. ` , { guildId , requestId } )
2023-06-10 14:23:10 +02:00
return allMovies
}
const movies : BaseItemDto [ ] = [ ]
for ( let i = 0 ; i < count ; i ++ ) {
2023-06-15 21:56:15 +02:00
const index = Math . floor ( Math . random ( ) * allMovies . length )
2023-06-10 14:23:10 +02:00
movies . push ( . . . allMovies . splice ( index , 1 ) ) // maybe out of bounds? ?
}
return movies
}
2023-04-16 02:04:08 +02:00
}
2023-06-10 14:23:10 +02:00
export enum UserUpsertResult { enabled , created }
2023-05-04 23:34:53 +02:00