Skip to content

Commit

Permalink
feat: Jellyfin/Emby server type setup (#685)
Browse files Browse the repository at this point in the history
* feat: add Media Server Selection to Setup Page

Introduce the ability to select the media server type on the setup page. Users can now choose their
preferred media server (e.g., Plex through the Plex sign-in or Emby/Jellyfin sign-in to select
either Emby or Jellyfin). The selected media server type is then reflected in the application
settings. This enhancement provides users with increased flexibility and customization options
during the initial setup process, eliminating the need to rely on environment variables (which
cannot be set if using platforms like snaps). Existing Emby users, who use the environment variable,
should log out and log back in after updating to set their mediaServerType to Emby.

BREAKING CHANGE: This commit deprecates the JELLYFIN_TYPE variable to identify Emby media server and
instead rely on the mediaServerType that is set in the `settings.json`. Existing environment
variable users can log out and log back in to set the mediaServerType to `3` (Emby).

* feat(api): add severType to the api

BREAKING CHANGE: This adds a serverType to the `/auth/jellyfin` which requires a serverType to be
set (`jellyfin`/`emby`)

* refactor: use enums for serverType and rename selectedservice to serverType

* refactor(auth): jellyfin/emby authentication to set MediaServerType

* fix: issue page formatMessage for 4k media

* refactor: cleaner way of handling serverType change using MediaServerType instead of strings

instead of using strings now it will use MediaServerType enums for serverType

* revert: removed conditional render of the auto-request permission

reverts the conditional render toshow the auto-request permission if the mediaServerType was set to
Plex as this should be handled in a different PR and Cypress tests should be modified
accordingly(currently cypress test would fail if this conditional check is there)

* feat: add server type step to setup

* feat: migrate existing emby setups to use emby mediaServerType

* fix: scan jobs not running when media server type is emby

* fix: emby media server type migration

* refactor: change emby logo to full logo

* style: decrease emby logo size in setup screen

* refactor: use title case for servertype i18n message

* refactor(i18n): fix a typo

* refactor: use enums instead of numbers

* fix: remove old references to JELLYFIN_TYPE environment variable

* fix: go back to the last step when refresh the setup page

* fix: move "scanning in background" tip next to the scanning section

* fix: redirect the setup page when Jellyseerr is already setup

---------

Co-authored-by: Gauthier <[email protected]>
  • Loading branch information
fallenbagel and gauthier-th authored Aug 20, 2024
1 parent cfd1bc2 commit 15cb949
Show file tree
Hide file tree
Showing 30 changed files with 661 additions and 341 deletions.
4 changes: 0 additions & 4 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ module.exports = {
commitTag: process.env.COMMIT_TAG || 'local',
forceIpv4First: process.env.FORCE_IPV4_FIRST === 'true' ? 'true' : 'false',
},
publicRuntimeConfig: {
// Will be available on both server and client
JELLYFIN_TYPE: process.env.JELLYFIN_TYPE,
},
images: {
remotePatterns: [
{ hostname: 'gravatar.com' },
Expand Down
2 changes: 2 additions & 0 deletions overseerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3586,6 +3586,8 @@ paths:
type: string
email:
type: string
serverType:
type: number
required:
- username
- password
Expand Down
5 changes: 5 additions & 0 deletions server/constants/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ export enum MediaServerType {
EMBY,
NOT_CONFIGURED,
}

export enum ServerType {
JELLYFIN = 'Jellyfin',
EMBY = 'Emby',
}
1 change: 1 addition & 0 deletions server/constants/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum UserType {
PLEX = 1,
LOCAL = 2,
JELLYFIN = 3,
EMBY = 4,
}
5 changes: 3 additions & 2 deletions server/entity/Media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,10 @@ class Media {
}
} else {
const pageName =
process.env.JELLYFIN_TYPE === 'emby' ? 'item' : 'details';
getSettings().main.mediaServerType == MediaServerType.EMBY
? 'item'
: 'details';
const { serverId, externalHostname } = getSettings().jellyfin;

const jellyfinHost =
externalHostname && externalHostname.length > 0
? externalHostname
Expand Down
5 changes: 4 additions & 1 deletion server/lib/scanners/jellyfin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,10 @@ class JellyfinScanner {
public async run(): Promise<void> {
const settings = getSettings();

if (settings.main.mediaServerType != MediaServerType.JELLYFIN) {
if (
settings.main.mediaServerType != MediaServerType.JELLYFIN &&
settings.main.mediaServerType != MediaServerType.EMBY
) {
return;
}

Expand Down
17 changes: 17 additions & 0 deletions server/lib/settings/migrations/0002_emby_media_server_type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MediaServerType } from '@server/constants/server';
import type { AllSettings } from '@server/lib/settings';

const migrateHostname = (settings: any): AllSettings => {
const oldMediaServerType = settings.main.mediaServerType;
console.log('Migrating media server type', oldMediaServerType);
if (
oldMediaServerType === MediaServerType.JELLYFIN &&
process.env.JELLYFIN_TYPE === 'emby'
) {
settings.main.mediaServerType = MediaServerType.EMBY;
}

return settings;
};

export default migrateHostname;
92 changes: 60 additions & 32 deletions server/routes/auth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import JellyfinAPI from '@server/api/jellyfin';
import PlexTvAPI from '@server/api/plextv';
import { ApiErrorCode } from '@server/constants/error';
import { MediaServerType } from '@server/constants/server';
import { MediaServerType, ServerType } from '@server/constants/server';
import { UserType } from '@server/constants/user';
import { getRepository } from '@server/datasource';
import { User } from '@server/entity/User';
Expand Down Expand Up @@ -227,15 +227,20 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
urlBase?: string;
useSsl?: boolean;
email?: string;
serverType?: number;
};

//Make sure jellyfin login is enabled, but only if jellyfin is not already configured
//Make sure jellyfin login is enabled, but only if jellyfin && Emby is not already configured
if (
settings.main.mediaServerType !== MediaServerType.JELLYFIN &&
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED
settings.main.mediaServerType !== MediaServerType.EMBY &&
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED &&
settings.jellyfin.ip !== ''
) {
return res.status(500).json({ error: 'Jellyfin login is disabled' });
} else if (!body.username) {
}

if (!body.username) {
return res.status(500).json({ error: 'You must provide an username' });
} else if (settings.jellyfin.ip !== '' && body.hostname) {
return res
Expand Down Expand Up @@ -273,7 +278,8 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
}

// First we need to attempt to log the user in to jellyfin
const jellyfinserver = new JellyfinAPI(hostname, undefined, deviceId);
const jellyfinserver = new JellyfinAPI(hostname ?? '', undefined, deviceId);

const jellyfinHost =
externalHostname && externalHostname.length > 0
? externalHostname
Expand Down Expand Up @@ -317,22 +323,47 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
);

// User doesn't exist, and there are no users in the database, we'll create the user
// with admin permission
settings.main.mediaServerType = MediaServerType.JELLYFIN;
user = new User({
email: body.email || account.User.Name,
jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
permissions: Permission.ADMIN,
avatar: account.User.PrimaryImageTag
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: gravatarUrl(body.email || account.User.Name, {
default: 'mm',
size: 200,
}),
userType: UserType.JELLYFIN,
});
// with admin permissions
switch (body.serverType) {
case MediaServerType.EMBY:
settings.main.mediaServerType = MediaServerType.EMBY;
user = new User({
email: body.email || account.User.Name,
jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN,
avatar: account.User.PrimaryImageTag
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: gravatarUrl(body.email || account.User.Name, {
default: 'mm',
size: 200,
}),
userType: UserType.EMBY,
});
break;
case MediaServerType.JELLYFIN:
settings.main.mediaServerType = MediaServerType.JELLYFIN;
user = new User({
email: body.email || account.User.Name,
jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN,
avatar: account.User.PrimaryImageTag
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: gravatarUrl(body.email || account.User.Name, {
default: 'mm',
size: 200,
}),
userType: UserType.JELLYFIN,
});
break;
default:
throw new Error('select_server_type');
}

// Create an API key on Jellyfin from this admin user
const jellyfinClient = new JellyfinAPI(
Expand Down Expand Up @@ -361,12 +392,12 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
logger.info(
`Found matching ${
settings.main.mediaServerType === MediaServerType.JELLYFIN
? 'Jellyfin'
: 'Emby'
? ServerType.JELLYFIN
: ServerType.EMBY
} user; updating user with ${
settings.main.mediaServerType === MediaServerType.JELLYFIN
? 'Jellyfin'
: 'Emby'
? ServerType.JELLYFIN
: ServerType.EMBY
}`,
{
label: 'API',
Expand All @@ -389,12 +420,6 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
user.username = '';
}

// TODO: If JELLYFIN_TYPE is set to 'emby' then set mediaServerType to EMBY
// if (process.env.JELLYFIN_TYPE === 'emby') {
// settings.main.mediaServerType = MediaServerType.EMBY;
// settings.save();
// }

await userRepository.save(user);
} else if (!settings.main.newPlexLogin) {
logger.warn(
Expand Down Expand Up @@ -432,7 +457,10 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
default: 'mm',
size: 200,
}),
userType: UserType.JELLYFIN,
userType:
settings.main.mediaServerType === MediaServerType.JELLYFIN
? UserType.JELLYFIN
: UserType.EMBY,
});
//initialize Jellyfin/Emby users with local login
const passedExplicitPassword = body.password && body.password.length > 0;
Expand Down
47 changes: 47 additions & 0 deletions src/assets/services/emby-icon-only.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 15cb949

Please sign in to comment.