mirror of
https://github.com/sct/overseerr.git
synced 2025-12-27 00:34:56 +01:00
* refactor(jellyfinsettings): abstract jellyfin hostname, updated ui to reflect it, better validation This PR refactors and abstracts jellyfin hostname into, jellyfin ip, jellyfin port, jellyfin useSsl, and jellyfin urlBase. This makes it more consistent with how plex settings are stored as well. In addition, this improves validation as validation can be applied seperately to them instead of as one whole regex doing the work to validate the url. UI was updated to reflect this. BREAKING CHANGE: Jellyfin settings now does not include a hostname. Instead it abstracted it to ip, port, useSsl, and urlBase. However, migration of old settings to new settings should work automatically. * refactor: remove console logs and use getHostname and ApiErrorCodes * fix: store req.body jellyfin settings temporarily and store only if valid This should fix the issue where settings are saved even if the url was invalid. Now the settings will only be saved if the url is valid. Sort of like a test connection. * refactor: clean up commented out code * refactor(i18n): extract translation keys * fix(auth): auth failing with jellyfin login is disabled * fix(settings): jellyfin migrations replacing the rest of the settings * fix(settings): jellyfin hostname should be carried out if hostname exists * fix(settings): merging the wrong settings source * refactor(settings): use migrator for dynamic settings migrations * refactor(settingsmigrator): settings migration handler and the migrations * test(cypress): fix cypress tests failing cypress settings were lacking some of the jobs so when the startJobs() is called when the app starts, it was failing to schedule the jobs where their cron timings were not specified in the cypress settings. Therefore, this commit adds those jobs back. In addition, other setting options were added to keep cypress settings consistent with a normal user. * chore(prettierignore): ignore cypress/config/settings.cypress.json as it does not need prettier * chore(prettier): ran formatter on cypress config to fix format check error format check locally passes on this file. However, it fails during the github actions format check. Therefore, json language features formatter was run instead of prettier to see if that fixes the issue. * test(cypress): add only missing jobs to the cypress settings * ci: attempt at trying to get formatter to pass on cypress config json file * refactor: revert the changes brought to try and fix formatter added back the rest of the cypress settings and removed cypress settings from .prettierignore * refactor(settings): better erorr logging when jellyfin connection test fails in settings page
670 lines
17 KiB
TypeScript
670 lines
17 KiB
TypeScript
import { MediaServerType } from '@server/constants/server';
|
|
import { Permission } from '@server/lib/permissions';
|
|
import { runMigrations } from '@server/lib/settings/migrator';
|
|
import { randomUUID } from 'crypto';
|
|
import fs from 'fs';
|
|
import { merge } from 'lodash';
|
|
import path from 'path';
|
|
import webpush from 'web-push';
|
|
|
|
export interface Library {
|
|
id: string;
|
|
name: string;
|
|
enabled: boolean;
|
|
type: 'show' | 'movie';
|
|
lastScan?: number;
|
|
}
|
|
|
|
export interface Region {
|
|
iso_3166_1: string;
|
|
english_name: string;
|
|
name?: string;
|
|
}
|
|
|
|
export interface Language {
|
|
iso_639_1: string;
|
|
english_name: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface PlexSettings {
|
|
name: string;
|
|
machineId?: string;
|
|
ip: string;
|
|
port: number;
|
|
useSsl?: boolean;
|
|
libraries: Library[];
|
|
webAppUrl?: string;
|
|
}
|
|
|
|
export interface JellyfinSettings {
|
|
name: string;
|
|
ip: string;
|
|
port: number;
|
|
useSsl?: boolean;
|
|
urlBase?: string;
|
|
externalHostname?: string;
|
|
jellyfinForgotPasswordUrl?: string;
|
|
libraries: Library[];
|
|
serverId: string;
|
|
}
|
|
export interface TautulliSettings {
|
|
hostname?: string;
|
|
port?: number;
|
|
useSsl?: boolean;
|
|
urlBase?: string;
|
|
apiKey?: string;
|
|
externalUrl?: string;
|
|
}
|
|
|
|
export interface DVRSettings {
|
|
id: number;
|
|
name: string;
|
|
hostname: string;
|
|
port: number;
|
|
apiKey: string;
|
|
useSsl: boolean;
|
|
baseUrl?: string;
|
|
activeProfileId: number;
|
|
activeProfileName: string;
|
|
activeDirectory: string;
|
|
tags: number[];
|
|
is4k: boolean;
|
|
isDefault: boolean;
|
|
externalUrl?: string;
|
|
syncEnabled: boolean;
|
|
preventSearch: boolean;
|
|
tagRequests: boolean;
|
|
}
|
|
|
|
export interface RadarrSettings extends DVRSettings {
|
|
minimumAvailability: string;
|
|
}
|
|
|
|
export interface SonarrSettings extends DVRSettings {
|
|
seriesType: 'standard' | 'daily' | 'anime';
|
|
animeSeriesType: 'standard' | 'daily' | 'anime';
|
|
activeAnimeProfileId?: number;
|
|
activeAnimeProfileName?: string;
|
|
activeAnimeDirectory?: string;
|
|
activeAnimeLanguageProfileId?: number;
|
|
activeLanguageProfileId?: number;
|
|
animeTags?: number[];
|
|
enableSeasonFolders: boolean;
|
|
}
|
|
|
|
interface Quota {
|
|
quotaLimit?: number;
|
|
quotaDays?: number;
|
|
}
|
|
|
|
export interface MainSettings {
|
|
apiKey: string;
|
|
applicationTitle: string;
|
|
applicationUrl: string;
|
|
csrfProtection: boolean;
|
|
cacheImages: boolean;
|
|
defaultPermissions: number;
|
|
defaultQuotas: {
|
|
movie: Quota;
|
|
tv: Quota;
|
|
};
|
|
hideAvailable: boolean;
|
|
localLogin: boolean;
|
|
newPlexLogin: boolean;
|
|
region: string;
|
|
originalLanguage: string;
|
|
trustProxy: boolean;
|
|
mediaServerType: number;
|
|
partialRequestsEnabled: boolean;
|
|
locale: string;
|
|
}
|
|
|
|
interface PublicSettings {
|
|
initialized: boolean;
|
|
}
|
|
|
|
interface FullPublicSettings extends PublicSettings {
|
|
applicationTitle: string;
|
|
applicationUrl: string;
|
|
hideAvailable: boolean;
|
|
localLogin: boolean;
|
|
movie4kEnabled: boolean;
|
|
series4kEnabled: boolean;
|
|
region: string;
|
|
originalLanguage: string;
|
|
mediaServerType: number;
|
|
jellyfinExternalHost?: string;
|
|
jellyfinForgotPasswordUrl?: string;
|
|
jellyfinServerName?: string;
|
|
partialRequestsEnabled: boolean;
|
|
cacheImages: boolean;
|
|
vapidPublic: string;
|
|
enablePushRegistration: boolean;
|
|
locale: string;
|
|
emailEnabled: boolean;
|
|
userEmailRequired: boolean;
|
|
newPlexLogin: boolean;
|
|
}
|
|
|
|
export interface NotificationAgentConfig {
|
|
enabled: boolean;
|
|
types?: number;
|
|
options: Record<string, unknown>;
|
|
}
|
|
export interface NotificationAgentDiscord extends NotificationAgentConfig {
|
|
options: {
|
|
botUsername?: string;
|
|
botAvatarUrl?: string;
|
|
webhookUrl: string;
|
|
enableMentions: boolean;
|
|
};
|
|
}
|
|
|
|
export interface NotificationAgentSlack extends NotificationAgentConfig {
|
|
options: {
|
|
webhookUrl: string;
|
|
};
|
|
}
|
|
|
|
export interface NotificationAgentEmail extends NotificationAgentConfig {
|
|
options: {
|
|
userEmailRequired: boolean;
|
|
emailFrom: string;
|
|
smtpHost: string;
|
|
smtpPort: number;
|
|
secure: boolean;
|
|
ignoreTls: boolean;
|
|
requireTls: boolean;
|
|
authUser?: string;
|
|
authPass?: string;
|
|
allowSelfSigned: boolean;
|
|
senderName: string;
|
|
pgpPrivateKey?: string;
|
|
pgpPassword?: string;
|
|
};
|
|
}
|
|
|
|
export interface NotificationAgentLunaSea extends NotificationAgentConfig {
|
|
options: {
|
|
webhookUrl: string;
|
|
profileName?: string;
|
|
};
|
|
}
|
|
|
|
export interface NotificationAgentTelegram extends NotificationAgentConfig {
|
|
options: {
|
|
botUsername?: string;
|
|
botAPI: string;
|
|
chatId: string;
|
|
sendSilently: boolean;
|
|
};
|
|
}
|
|
|
|
export interface NotificationAgentPushbullet extends NotificationAgentConfig {
|
|
options: {
|
|
accessToken: string;
|
|
channelTag?: string;
|
|
};
|
|
}
|
|
|
|
export interface NotificationAgentPushover extends NotificationAgentConfig {
|
|
options: {
|
|
accessToken: string;
|
|
userToken: string;
|
|
sound: string;
|
|
};
|
|
}
|
|
|
|
export interface NotificationAgentWebhook extends NotificationAgentConfig {
|
|
options: {
|
|
webhookUrl: string;
|
|
jsonPayload: string;
|
|
authHeader?: string;
|
|
};
|
|
}
|
|
|
|
export interface NotificationAgentGotify extends NotificationAgentConfig {
|
|
options: {
|
|
url: string;
|
|
token: string;
|
|
};
|
|
}
|
|
|
|
export enum NotificationAgentKey {
|
|
DISCORD = 'discord',
|
|
EMAIL = 'email',
|
|
GOTIFY = 'gotify',
|
|
PUSHBULLET = 'pushbullet',
|
|
PUSHOVER = 'pushover',
|
|
SLACK = 'slack',
|
|
TELEGRAM = 'telegram',
|
|
WEBHOOK = 'webhook',
|
|
WEBPUSH = 'webpush',
|
|
}
|
|
|
|
interface NotificationAgents {
|
|
discord: NotificationAgentDiscord;
|
|
email: NotificationAgentEmail;
|
|
gotify: NotificationAgentGotify;
|
|
lunasea: NotificationAgentLunaSea;
|
|
pushbullet: NotificationAgentPushbullet;
|
|
pushover: NotificationAgentPushover;
|
|
slack: NotificationAgentSlack;
|
|
telegram: NotificationAgentTelegram;
|
|
webhook: NotificationAgentWebhook;
|
|
webpush: NotificationAgentConfig;
|
|
}
|
|
|
|
interface NotificationSettings {
|
|
agents: NotificationAgents;
|
|
}
|
|
|
|
interface JobSettings {
|
|
schedule: string;
|
|
}
|
|
|
|
export type JobId =
|
|
| 'plex-recently-added-scan'
|
|
| 'plex-full-scan'
|
|
| 'plex-watchlist-sync'
|
|
| 'radarr-scan'
|
|
| 'sonarr-scan'
|
|
| 'download-sync'
|
|
| 'download-sync-reset'
|
|
| 'jellyfin-recently-added-scan'
|
|
| 'jellyfin-full-scan'
|
|
| 'image-cache-cleanup'
|
|
| 'availability-sync';
|
|
|
|
export interface AllSettings {
|
|
clientId: string;
|
|
vapidPublic: string;
|
|
vapidPrivate: string;
|
|
main: MainSettings;
|
|
plex: PlexSettings;
|
|
jellyfin: JellyfinSettings;
|
|
tautulli: TautulliSettings;
|
|
radarr: RadarrSettings[];
|
|
sonarr: SonarrSettings[];
|
|
public: PublicSettings;
|
|
notifications: NotificationSettings;
|
|
jobs: Record<JobId, JobSettings>;
|
|
}
|
|
|
|
const SETTINGS_PATH = process.env.CONFIG_DIRECTORY
|
|
? `${process.env.CONFIG_DIRECTORY}/settings.json`
|
|
: path.join(__dirname, '../../../config/settings.json');
|
|
|
|
class Settings {
|
|
private data: AllSettings;
|
|
|
|
constructor(initialSettings?: AllSettings) {
|
|
this.data = {
|
|
clientId: randomUUID(),
|
|
vapidPrivate: '',
|
|
vapidPublic: '',
|
|
main: {
|
|
apiKey: '',
|
|
applicationTitle: 'Jellyseerr',
|
|
applicationUrl: '',
|
|
csrfProtection: false,
|
|
cacheImages: false,
|
|
defaultPermissions: Permission.REQUEST,
|
|
defaultQuotas: {
|
|
movie: {},
|
|
tv: {},
|
|
},
|
|
hideAvailable: false,
|
|
localLogin: true,
|
|
newPlexLogin: true,
|
|
region: '',
|
|
originalLanguage: '',
|
|
trustProxy: false,
|
|
mediaServerType: MediaServerType.NOT_CONFIGURED,
|
|
partialRequestsEnabled: true,
|
|
locale: 'en',
|
|
},
|
|
plex: {
|
|
name: '',
|
|
ip: '',
|
|
port: 32400,
|
|
useSsl: false,
|
|
libraries: [],
|
|
},
|
|
jellyfin: {
|
|
name: '',
|
|
ip: '',
|
|
port: 8096,
|
|
useSsl: false,
|
|
urlBase: '',
|
|
externalHostname: '',
|
|
jellyfinForgotPasswordUrl: '',
|
|
libraries: [],
|
|
serverId: '',
|
|
},
|
|
tautulli: {},
|
|
radarr: [],
|
|
sonarr: [],
|
|
public: {
|
|
initialized: false,
|
|
},
|
|
notifications: {
|
|
agents: {
|
|
email: {
|
|
enabled: false,
|
|
options: {
|
|
userEmailRequired: false,
|
|
emailFrom: '',
|
|
smtpHost: '',
|
|
smtpPort: 587,
|
|
secure: false,
|
|
ignoreTls: false,
|
|
requireTls: false,
|
|
allowSelfSigned: false,
|
|
senderName: 'Jellyseerr',
|
|
},
|
|
},
|
|
discord: {
|
|
enabled: false,
|
|
types: 0,
|
|
options: {
|
|
webhookUrl: '',
|
|
enableMentions: true,
|
|
},
|
|
},
|
|
lunasea: {
|
|
enabled: false,
|
|
types: 0,
|
|
options: {
|
|
webhookUrl: '',
|
|
},
|
|
},
|
|
slack: {
|
|
enabled: false,
|
|
types: 0,
|
|
options: {
|
|
webhookUrl: '',
|
|
},
|
|
},
|
|
telegram: {
|
|
enabled: false,
|
|
types: 0,
|
|
options: {
|
|
botAPI: '',
|
|
chatId: '',
|
|
sendSilently: false,
|
|
},
|
|
},
|
|
pushbullet: {
|
|
enabled: false,
|
|
types: 0,
|
|
options: {
|
|
accessToken: '',
|
|
},
|
|
},
|
|
pushover: {
|
|
enabled: false,
|
|
types: 0,
|
|
options: {
|
|
accessToken: '',
|
|
userToken: '',
|
|
sound: '',
|
|
},
|
|
},
|
|
webhook: {
|
|
enabled: false,
|
|
types: 0,
|
|
options: {
|
|
webhookUrl: '',
|
|
jsonPayload:
|
|
'IntcbiAgXCJub3RpZmljYXRpb25fdHlwZVwiOiBcInt7bm90aWZpY2F0aW9uX3R5cGV9fVwiLFxuICBcImV2ZW50XCI6IFwie3tldmVudH19XCIsXG4gIFwic3ViamVjdFwiOiBcInt7c3ViamVjdH19XCIsXG4gIFwibWVzc2FnZVwiOiBcInt7bWVzc2FnZX19XCIsXG4gIFwiaW1hZ2VcIjogXCJ7e2ltYWdlfX1cIixcbiAgXCJ7e21lZGlhfX1cIjoge1xuICAgIFwibWVkaWFfdHlwZVwiOiBcInt7bWVkaWFfdHlwZX19XCIsXG4gICAgXCJ0bWRiSWRcIjogXCJ7e21lZGlhX3RtZGJpZH19XCIsXG4gICAgXCJ0dmRiSWRcIjogXCJ7e21lZGlhX3R2ZGJpZH19XCIsXG4gICAgXCJzdGF0dXNcIjogXCJ7e21lZGlhX3N0YXR1c319XCIsXG4gICAgXCJzdGF0dXM0a1wiOiBcInt7bWVkaWFfc3RhdHVzNGt9fVwiXG4gIH0sXG4gIFwie3tyZXF1ZXN0fX1cIjoge1xuICAgIFwicmVxdWVzdF9pZFwiOiBcInt7cmVxdWVzdF9pZH19XCIsXG4gICAgXCJyZXF1ZXN0ZWRCeV9lbWFpbFwiOiBcInt7cmVxdWVzdGVkQnlfZW1haWx9fVwiLFxuICAgIFwicmVxdWVzdGVkQnlfdXNlcm5hbWVcIjogXCJ7e3JlcXVlc3RlZEJ5X3VzZXJuYW1lfX1cIixcbiAgICBcInJlcXVlc3RlZEJ5X2F2YXRhclwiOiBcInt7cmVxdWVzdGVkQnlfYXZhdGFyfX1cIixcbiAgICBcInJlcXVlc3RlZEJ5X3NldHRpbmdzX2Rpc2NvcmRJZFwiOiBcInt7cmVxdWVzdGVkQnlfc2V0dGluZ3NfZGlzY29yZElkfX1cIixcbiAgICBcInJlcXVlc3RlZEJ5X3NldHRpbmdzX3RlbGVncmFtQ2hhdElkXCI6IFwie3tyZXF1ZXN0ZWRCeV9zZXR0aW5nc190ZWxlZ3JhbUNoYXRJZH19XCJcbiAgfSxcbiAgXCJ7e2lzc3VlfX1cIjoge1xuICAgIFwiaXNzdWVfaWRcIjogXCJ7e2lzc3VlX2lkfX1cIixcbiAgICBcImlzc3VlX3R5cGVcIjogXCJ7e2lzc3VlX3R5cGV9fVwiLFxuICAgIFwiaXNzdWVfc3RhdHVzXCI6IFwie3tpc3N1ZV9zdGF0dXN9fVwiLFxuICAgIFwicmVwb3J0ZWRCeV9lbWFpbFwiOiBcInt7cmVwb3J0ZWRCeV9lbWFpbH19XCIsXG4gICAgXCJyZXBvcnRlZEJ5X3VzZXJuYW1lXCI6IFwie3tyZXBvcnRlZEJ5X3VzZXJuYW1lfX1cIixcbiAgICBcInJlcG9ydGVkQnlfYXZhdGFyXCI6IFwie3tyZXBvcnRlZEJ5X2F2YXRhcn19XCIsXG4gICAgXCJyZXBvcnRlZEJ5X3NldHRpbmdzX2Rpc2NvcmRJZFwiOiBcInt7cmVwb3J0ZWRCeV9zZXR0aW5nc19kaXNjb3JkSWR9fVwiLFxuICAgIFwicmVwb3J0ZWRCeV9zZXR0aW5nc190ZWxlZ3JhbUNoYXRJZFwiOiBcInt7cmVwb3J0ZWRCeV9zZXR0aW5nc190ZWxlZ3JhbUNoYXRJZH19XCJcbiAgfSxcbiAgXCJ7e2NvbW1lbnR9fVwiOiB7XG4gICAgXCJjb21tZW50X21lc3NhZ2VcIjogXCJ7e2NvbW1lbnRfbWVzc2FnZX19XCIsXG4gICAgXCJjb21tZW50ZWRCeV9lbWFpbFwiOiBcInt7Y29tbWVudGVkQnlfZW1haWx9fVwiLFxuICAgIFwiY29tbWVudGVkQnlfdXNlcm5hbWVcIjogXCJ7e2NvbW1lbnRlZEJ5X3VzZXJuYW1lfX1cIixcbiAgICBcImNvbW1lbnRlZEJ5X2F2YXRhclwiOiBcInt7Y29tbWVudGVkQnlfYXZhdGFyfX1cIixcbiAgICBcImNvbW1lbnRlZEJ5X3NldHRpbmdzX2Rpc2NvcmRJZFwiOiBcInt7Y29tbWVudGVkQnlfc2V0dGluZ3NfZGlzY29yZElkfX1cIixcbiAgICBcImNvbW1lbnRlZEJ5X3NldHRpbmdzX3RlbGVncmFtQ2hhdElkXCI6IFwie3tjb21tZW50ZWRCeV9zZXR0aW5nc190ZWxlZ3JhbUNoYXRJZH19XCJcbiAgfSxcbiAgXCJ7e2V4dHJhfX1cIjogW11cbn0i',
|
|
},
|
|
},
|
|
webpush: {
|
|
enabled: false,
|
|
options: {},
|
|
},
|
|
gotify: {
|
|
enabled: false,
|
|
types: 0,
|
|
options: {
|
|
url: '',
|
|
token: '',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
jobs: {
|
|
'plex-recently-added-scan': {
|
|
schedule: '0 */5 * * * *',
|
|
},
|
|
'plex-full-scan': {
|
|
schedule: '0 0 3 * * *',
|
|
},
|
|
'plex-watchlist-sync': {
|
|
schedule: '0 */10 * * * *',
|
|
},
|
|
'radarr-scan': {
|
|
schedule: '0 0 4 * * *',
|
|
},
|
|
'sonarr-scan': {
|
|
schedule: '0 30 4 * * *',
|
|
},
|
|
'availability-sync': {
|
|
schedule: '0 0 5 * * *',
|
|
},
|
|
'download-sync': {
|
|
schedule: '0 * * * * *',
|
|
},
|
|
'download-sync-reset': {
|
|
schedule: '0 0 1 * * *',
|
|
},
|
|
'jellyfin-recently-added-scan': {
|
|
schedule: '0 */5 * * * *',
|
|
},
|
|
'jellyfin-full-scan': {
|
|
schedule: '0 0 3 * * *',
|
|
},
|
|
'image-cache-cleanup': {
|
|
schedule: '0 0 5 * * *',
|
|
},
|
|
},
|
|
};
|
|
if (initialSettings) {
|
|
this.data = merge(this.data, initialSettings);
|
|
}
|
|
}
|
|
|
|
get main(): MainSettings {
|
|
if (!this.data.main.apiKey) {
|
|
this.data.main.apiKey = this.generateApiKey();
|
|
this.save();
|
|
}
|
|
return this.data.main;
|
|
}
|
|
|
|
set main(data: MainSettings) {
|
|
this.data.main = data;
|
|
}
|
|
|
|
get plex(): PlexSettings {
|
|
return this.data.plex;
|
|
}
|
|
|
|
set plex(data: PlexSettings) {
|
|
this.data.plex = data;
|
|
}
|
|
|
|
get jellyfin(): JellyfinSettings {
|
|
return this.data.jellyfin;
|
|
}
|
|
|
|
set jellyfin(data: JellyfinSettings) {
|
|
this.data.jellyfin = data;
|
|
}
|
|
|
|
get tautulli(): TautulliSettings {
|
|
return this.data.tautulli;
|
|
}
|
|
|
|
set tautulli(data: TautulliSettings) {
|
|
this.data.tautulli = data;
|
|
}
|
|
|
|
get radarr(): RadarrSettings[] {
|
|
return this.data.radarr;
|
|
}
|
|
|
|
set radarr(data: RadarrSettings[]) {
|
|
this.data.radarr = data;
|
|
}
|
|
|
|
get sonarr(): SonarrSettings[] {
|
|
return this.data.sonarr;
|
|
}
|
|
|
|
set sonarr(data: SonarrSettings[]) {
|
|
this.data.sonarr = data;
|
|
}
|
|
|
|
get public(): PublicSettings {
|
|
return this.data.public;
|
|
}
|
|
|
|
set public(data: PublicSettings) {
|
|
this.data.public = data;
|
|
}
|
|
|
|
get fullPublicSettings(): FullPublicSettings {
|
|
return {
|
|
...this.data.public,
|
|
applicationTitle: this.data.main.applicationTitle,
|
|
applicationUrl: this.data.main.applicationUrl,
|
|
hideAvailable: this.data.main.hideAvailable,
|
|
localLogin: this.data.main.localLogin,
|
|
jellyfinForgotPasswordUrl: this.data.jellyfin.jellyfinForgotPasswordUrl,
|
|
movie4kEnabled: this.data.radarr.some(
|
|
(radarr) => radarr.is4k && radarr.isDefault
|
|
),
|
|
series4kEnabled: this.data.sonarr.some(
|
|
(sonarr) => sonarr.is4k && sonarr.isDefault
|
|
),
|
|
region: this.data.main.region,
|
|
originalLanguage: this.data.main.originalLanguage,
|
|
mediaServerType: this.main.mediaServerType,
|
|
partialRequestsEnabled: this.data.main.partialRequestsEnabled,
|
|
cacheImages: this.data.main.cacheImages,
|
|
vapidPublic: this.vapidPublic,
|
|
enablePushRegistration: this.data.notifications.agents.webpush.enabled,
|
|
locale: this.data.main.locale,
|
|
emailEnabled: this.data.notifications.agents.email.enabled,
|
|
userEmailRequired:
|
|
this.data.notifications.agents.email.options.userEmailRequired,
|
|
newPlexLogin: this.data.main.newPlexLogin,
|
|
};
|
|
}
|
|
|
|
get notifications(): NotificationSettings {
|
|
return this.data.notifications;
|
|
}
|
|
|
|
set notifications(data: NotificationSettings) {
|
|
this.data.notifications = data;
|
|
}
|
|
|
|
get jobs(): Record<JobId, JobSettings> {
|
|
return this.data.jobs;
|
|
}
|
|
|
|
set jobs(data: Record<JobId, JobSettings>) {
|
|
this.data.jobs = data;
|
|
}
|
|
|
|
get clientId(): string {
|
|
if (!this.data.clientId) {
|
|
this.data.clientId = randomUUID();
|
|
this.save();
|
|
}
|
|
|
|
return this.data.clientId;
|
|
}
|
|
|
|
get vapidPublic(): string {
|
|
this.generateVapidKeys();
|
|
|
|
return this.data.vapidPublic;
|
|
}
|
|
|
|
get vapidPrivate(): string {
|
|
this.generateVapidKeys();
|
|
|
|
return this.data.vapidPrivate;
|
|
}
|
|
|
|
public regenerateApiKey(): MainSettings {
|
|
this.main.apiKey = this.generateApiKey();
|
|
this.save();
|
|
return this.main;
|
|
}
|
|
|
|
private generateApiKey(): string {
|
|
return Buffer.from(`${Date.now()}${randomUUID()}`).toString('base64');
|
|
}
|
|
|
|
private generateVapidKeys(force = false): void {
|
|
if (!this.data.vapidPublic || !this.data.vapidPrivate || force) {
|
|
const vapidKeys = webpush.generateVAPIDKeys();
|
|
this.data.vapidPrivate = vapidKeys.privateKey;
|
|
this.data.vapidPublic = vapidKeys.publicKey;
|
|
this.save();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Settings Load
|
|
*
|
|
* This will load settings from file unless an optional argument of the object structure
|
|
* is passed in.
|
|
* @param overrideSettings If passed in, will override all existing settings with these
|
|
* values
|
|
*/
|
|
public load(overrideSettings?: AllSettings): Settings {
|
|
if (overrideSettings) {
|
|
this.data = overrideSettings;
|
|
return this;
|
|
}
|
|
|
|
if (!fs.existsSync(SETTINGS_PATH)) {
|
|
this.save();
|
|
}
|
|
const data = fs.readFileSync(SETTINGS_PATH, 'utf-8');
|
|
|
|
if (data) {
|
|
const parsedJson = JSON.parse(data);
|
|
this.data = runMigrations(parsedJson);
|
|
|
|
this.data = merge(this.data, parsedJson);
|
|
|
|
this.save();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public save(): void {
|
|
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(this.data, undefined, ' '));
|
|
}
|
|
}
|
|
|
|
let settings: Settings | undefined;
|
|
|
|
export const getSettings = (initialSettings?: AllSettings): Settings => {
|
|
if (!settings) {
|
|
settings = new Settings(initialSettings);
|
|
}
|
|
|
|
return settings;
|
|
};
|
|
|
|
export default Settings;
|