2023-05-04 23:34:53 +02:00
import { GuildMember } from "discord.js" ;
2023-06-15 22:33:22 +02:00
import { JellyfinConfig , Maybe , PermissionLevel } from "../interfaces" ;
2023-06-09 23:56:45 +02:00
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-23 21:23:54 +02:00
import { BaseItemDto , UpdateUserPasswordRequest , UpdateUserPolicyRequest } 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 {
2023-06-24 21:09:56 +02:00
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 , requestId : string , guildId? : 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 ) {
if ( createResult . policy ) {
this . setUserPermissions ( createResult , requestId , guildId )
}
( 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 setUserPermissions ( user : UserDto , requestId : string , guildId? : string ) {
if ( ! user . policy || ! user . id ) {
logger . error ( ` Cannot update user policy. User ${ user . name } has no policy to modify ` , { guildId , requestId } )
return
}
user . policy . enableVideoPlaybackTranscoding = false
const operation : UpdateUserPolicyRequest = {
. . . user . policy ,
enableVideoPlaybackTranscoding : false
}
const request : UpdateUserPolicyOperationRequest = {
userId : user.id ,
updateUserPolicyRequest : operation
}
this . userApi . updateUserPolicy ( request )
}
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 ( m ovie = > movie . name && movie . name . length > 0 ) . map ( movie = > < string > movie . name )
movieCount = movieNames . length
} while ( movieCount < count )
return movieNames
}
2023-06-16 20:15:36 +02:00
2023-06-10 14:23:10 +02:00
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