mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-12 20:31:36 +00:00
449 lines
18 KiB
JavaScript
449 lines
18 KiB
JavaScript
const packageJson = require('../../../package.json')
|
|
const { BookshelfView } = require('../../utils/constants')
|
|
const Logger = require('../../Logger')
|
|
|
|
/**
|
|
* @openapi
|
|
* components:
|
|
* schemas:
|
|
* serverSettings:
|
|
* type: object
|
|
* properties:
|
|
* id:
|
|
* description: The ID of the server settings.
|
|
* type: string
|
|
* example: server-settings
|
|
* scannerFindCovers:
|
|
* description: Whether the scanner will attempt to find a cover if your audiobook does not have an embedded cover or a cover image inside the folder. Note that This will extend scan time.
|
|
* type: boolean
|
|
* example: false
|
|
* scannerCoverProvider:
|
|
* description: If scannerFindCovers is true, which metadata provider to use. See Metadata Providers for options.
|
|
* type: string
|
|
* example: google
|
|
* scannerParseSubtitle:
|
|
* description: Whether to extract subtitles from audiobook folder names. Subtitles must be separated by -, i.e. /audiobooks/Book Title - A Subtitle Here/ has the subtitle A Subtitle Here.
|
|
* type: boolean
|
|
* example: false
|
|
* scannerPreferAudioMetadata:
|
|
* description: Whether to use audio file ID3 meta tags instead of folder names for book details.
|
|
* type: boolean
|
|
* example: false
|
|
* scannerPreferOpfMetadata:
|
|
* description: Whether to use OPF file metadata instead of folder names for book details.
|
|
* type: boolean
|
|
* example: false
|
|
* scannerPreferMatchedMetadata:
|
|
* description: Whether matched data will override item details when using Quick Match. By default, Quick Match will only fill in missing details.
|
|
* type: boolean
|
|
* example: false
|
|
* scannerDisableWatcher:
|
|
* description: Whether to disable the automatic adding/updating of items when file changes are detected. Requires server restart for changes to take effect.
|
|
* type: boolean
|
|
* example: true
|
|
* scannerPreferOverdriveMediaMarker:
|
|
* description: Whether to use the custom metadata in MP3 files from Overdrive for chapter timings automatically.
|
|
* type: boolean
|
|
* example: false
|
|
* scannerUseTone:
|
|
* description: Should use tone to extract metadata
|
|
* type: boolean
|
|
* example: false
|
|
* storeCoverWithItem:
|
|
* description: Whether to store covers in the library item's folder. By default, covers are stored in /metadata/items. Only one file named cover will be kept.
|
|
* type: boolean
|
|
* example: false
|
|
* storeMetadataWithItem:
|
|
* description: Whether to store metadata files in the library item's folder. By default, metadata files are stored in /metadata/items. Uses the .abs file extension.
|
|
* type: boolean
|
|
* example: false
|
|
* metadataFileFormat:
|
|
* description: Must be either json or abs
|
|
* type: string
|
|
* example: json
|
|
* rateLimitLoginRequests:
|
|
* description: The maximum number of login requests per rateLimitLoginWindow.
|
|
* type: integer
|
|
* example: 10
|
|
* rateLimitLoginWindow:
|
|
* description: The length (in ms) of each login rate limit window.
|
|
* type: integer
|
|
* example: 600000
|
|
* backupSchedule:
|
|
* description: The cron expression for when to do automatic backups.
|
|
* type: string
|
|
* example: 30 1 * * *
|
|
* backupsToKeep:
|
|
* description: The number of backups to keep.
|
|
* type: integer
|
|
* example: 2
|
|
* maxBackupSize:
|
|
* description: The maximum backup size (in GB) before they fail, a safeguard against misconfiguration.
|
|
* type: integer
|
|
* example: 1
|
|
* backupMetadataCovers:
|
|
* description: Whether backups should include library item covers and author images located in metadata.
|
|
* type: boolean
|
|
* example: true
|
|
* loggerDailyLogsToKeep:
|
|
* description: The number of daily logs to keep.
|
|
* type: integer
|
|
* example: 7
|
|
* loggerScannerLogsToKeep:
|
|
* description: The number of scanner logs to keep.
|
|
* type: integer
|
|
* example: 2
|
|
* homeBookshelfView:
|
|
* description: Whether the home page should use a skeuomorphic design with wooden shelves.
|
|
* type: integer
|
|
* example: 1
|
|
* bookshelfView:
|
|
* description: Whether other bookshelf pages should use a skeuomorphic design with wooden shelves.
|
|
* type: integer
|
|
* example: 1
|
|
* sortingIgnorePrefix:
|
|
* description: Whether to ignore prefixes when sorting. For example, for the prefix the, the book title The Book Title would sort as Book Title, The.
|
|
* type: boolean
|
|
* example: false
|
|
* sortingPrefixes:
|
|
* description: If sortingIgnorePrefix is true, what prefixes to ignore.
|
|
* type: array
|
|
* items:
|
|
* type: string
|
|
* example: the
|
|
* - a
|
|
* chromecastEnabled:
|
|
* description: Whether to enable streaming to Chromecast devices.
|
|
* type: boolean
|
|
* example: false
|
|
* dateFormat:
|
|
* description: What date format to use. Options are MM/dd/yyyy, dd/MM/yyyy, dd.MM.yyyy, yyyy-MM-dd, MMM do, yyyy, MMMM do, yyyy, dd MMM yyyy, or dd MMMM yyyy.
|
|
* type: string
|
|
* example: MM/dd/yyyy
|
|
* timeFormat:
|
|
* description: What time format to use. Options are HH:mm (24-hour) and h:mma (am/pm).
|
|
* type: string
|
|
* example: HH:mm
|
|
* language:
|
|
* description: The default server language.
|
|
* type: string
|
|
* example: en-us
|
|
* logLevel:
|
|
* description: What log level the server should use when logging. 1 for debug, 2 for info, or 3 for warnings.
|
|
* type: integer
|
|
* example: 2
|
|
* version:
|
|
* description: The server's version.
|
|
* type: string
|
|
* example: 2.2.5
|
|
*/
|
|
class ServerSettings {
|
|
constructor(settings) {
|
|
this.id = 'server-settings'
|
|
this.tokenSecret = null
|
|
|
|
// Scanner
|
|
this.scannerParseSubtitle = false
|
|
this.scannerFindCovers = false
|
|
this.scannerCoverProvider = 'google'
|
|
this.scannerPreferMatchedMetadata = false
|
|
this.scannerDisableWatcher = false
|
|
|
|
// Metadata - choose to store inside users library item folder
|
|
this.storeCoverWithItem = false
|
|
this.storeMetadataWithItem = false
|
|
this.metadataFileFormat = 'json'
|
|
|
|
// Security/Rate limits
|
|
this.rateLimitLoginRequests = 10
|
|
this.rateLimitLoginWindow = 10 * 60 * 1000 // 10 Minutes
|
|
|
|
// Backups
|
|
this.backupSchedule = false // If false then auto-backups are disabled
|
|
this.backupsToKeep = 2
|
|
this.maxBackupSize = 1
|
|
|
|
// Logger
|
|
this.loggerDailyLogsToKeep = 7
|
|
this.loggerScannerLogsToKeep = 2
|
|
|
|
// Bookshelf Display
|
|
this.homeBookshelfView = BookshelfView.DETAIL
|
|
this.bookshelfView = BookshelfView.DETAIL
|
|
|
|
// Podcasts
|
|
this.podcastEpisodeSchedule = '0 * * * *' // Every hour
|
|
|
|
// Sorting
|
|
this.sortingIgnorePrefix = false
|
|
this.sortingPrefixes = ['the', 'a']
|
|
|
|
// Misc Flags
|
|
this.chromecastEnabled = false
|
|
this.dateFormat = 'MM/dd/yyyy'
|
|
this.timeFormat = 'HH:mm'
|
|
this.language = 'en-us'
|
|
|
|
this.logLevel = Logger.logLevel
|
|
|
|
this.version = packageJson.version
|
|
this.buildNumber = packageJson.buildNumber
|
|
|
|
// Auth settings
|
|
this.authLoginCustomMessage = null
|
|
this.authActiveAuthMethods = ['local']
|
|
|
|
// openid settings
|
|
this.authOpenIDIssuerURL = null
|
|
this.authOpenIDAuthorizationURL = null
|
|
this.authOpenIDTokenURL = null
|
|
this.authOpenIDUserInfoURL = null
|
|
this.authOpenIDJwksURL = null
|
|
this.authOpenIDLogoutURL = null
|
|
this.authOpenIDClientID = null
|
|
this.authOpenIDClientSecret = null
|
|
this.authOpenIDButtonText = 'Login with OpenId'
|
|
this.authOpenIDAutoLaunch = false
|
|
this.authOpenIDAutoRegister = false
|
|
this.authOpenIDMatchExistingBy = null
|
|
this.authOpenIDMobileRedirectURIs = ['audiobookshelf://oauth']
|
|
|
|
if (settings) {
|
|
this.construct(settings)
|
|
}
|
|
}
|
|
|
|
construct(settings) {
|
|
this.tokenSecret = settings.tokenSecret
|
|
this.scannerFindCovers = !!settings.scannerFindCovers
|
|
this.scannerCoverProvider = settings.scannerCoverProvider || 'google'
|
|
this.scannerParseSubtitle = settings.scannerParseSubtitle
|
|
this.scannerPreferMatchedMetadata = !!settings.scannerPreferMatchedMetadata
|
|
this.scannerDisableWatcher = !!settings.scannerDisableWatcher
|
|
|
|
this.storeCoverWithItem = !!settings.storeCoverWithItem
|
|
this.storeMetadataWithItem = !!settings.storeMetadataWithItem
|
|
this.metadataFileFormat = settings.metadataFileFormat || 'json'
|
|
|
|
this.rateLimitLoginRequests = !isNaN(settings.rateLimitLoginRequests) ? Number(settings.rateLimitLoginRequests) : 10
|
|
this.rateLimitLoginWindow = !isNaN(settings.rateLimitLoginWindow) ? Number(settings.rateLimitLoginWindow) : 10 * 60 * 1000 // 10 Minutes
|
|
|
|
this.backupSchedule = settings.backupSchedule || false
|
|
this.backupsToKeep = settings.backupsToKeep || 2
|
|
this.maxBackupSize = settings.maxBackupSize || 1
|
|
|
|
this.loggerDailyLogsToKeep = settings.loggerDailyLogsToKeep || 7
|
|
this.loggerScannerLogsToKeep = settings.loggerScannerLogsToKeep || 2
|
|
|
|
this.homeBookshelfView = settings.homeBookshelfView || BookshelfView.STANDARD
|
|
this.bookshelfView = settings.bookshelfView || BookshelfView.STANDARD
|
|
|
|
this.sortingIgnorePrefix = !!settings.sortingIgnorePrefix
|
|
this.sortingPrefixes = settings.sortingPrefixes || ['the']
|
|
this.chromecastEnabled = !!settings.chromecastEnabled
|
|
this.dateFormat = settings.dateFormat || 'MM/dd/yyyy'
|
|
this.timeFormat = settings.timeFormat || 'HH:mm'
|
|
this.language = settings.language || 'en-us'
|
|
this.logLevel = settings.logLevel || Logger.logLevel
|
|
this.version = settings.version || null
|
|
this.buildNumber = settings.buildNumber || 0 // Added v2.4.5
|
|
|
|
this.authLoginCustomMessage = settings.authLoginCustomMessage || null // Added v2.7.3
|
|
this.authActiveAuthMethods = settings.authActiveAuthMethods || ['local']
|
|
|
|
this.authOpenIDIssuerURL = settings.authOpenIDIssuerURL || null
|
|
this.authOpenIDAuthorizationURL = settings.authOpenIDAuthorizationURL || null
|
|
this.authOpenIDTokenURL = settings.authOpenIDTokenURL || null
|
|
this.authOpenIDUserInfoURL = settings.authOpenIDUserInfoURL || null
|
|
this.authOpenIDJwksURL = settings.authOpenIDJwksURL || null
|
|
this.authOpenIDLogoutURL = settings.authOpenIDLogoutURL || null
|
|
this.authOpenIDClientID = settings.authOpenIDClientID || null
|
|
this.authOpenIDClientSecret = settings.authOpenIDClientSecret || null
|
|
this.authOpenIDButtonText = settings.authOpenIDButtonText || 'Login with OpenId'
|
|
this.authOpenIDAutoLaunch = !!settings.authOpenIDAutoLaunch
|
|
this.authOpenIDAutoRegister = !!settings.authOpenIDAutoRegister
|
|
this.authOpenIDMatchExistingBy = settings.authOpenIDMatchExistingBy || null
|
|
this.authOpenIDMobileRedirectURIs = settings.authOpenIDMobileRedirectURIs || ['audiobookshelf://oauth']
|
|
|
|
if (!Array.isArray(this.authActiveAuthMethods)) {
|
|
this.authActiveAuthMethods = ['local']
|
|
}
|
|
|
|
// remove uninitialized methods
|
|
// OpenID
|
|
if (this.authActiveAuthMethods.includes('openid') && !this.isOpenIDAuthSettingsValid) {
|
|
this.authActiveAuthMethods.splice(this.authActiveAuthMethods.indexOf('openid', 0), 1)
|
|
}
|
|
|
|
// fallback to local
|
|
if (!Array.isArray(this.authActiveAuthMethods) || this.authActiveAuthMethods.length == 0) {
|
|
this.authActiveAuthMethods = ['local']
|
|
}
|
|
|
|
// Migrations
|
|
if (settings.storeCoverWithBook != undefined) { // storeCoverWithBook was renamed to storeCoverWithItem in 2.0.0
|
|
this.storeCoverWithItem = !!settings.storeCoverWithBook
|
|
}
|
|
if (settings.storeMetadataWithBook != undefined) { // storeMetadataWithBook was renamed to storeMetadataWithItem in 2.0.0
|
|
this.storeMetadataWithItem = !!settings.storeMetadataWithBook
|
|
}
|
|
if (settings.homeBookshelfView == undefined) { // homeBookshelfView was added in 2.1.3
|
|
this.homeBookshelfView = settings.bookshelfView
|
|
}
|
|
if (settings.metadataFileFormat == undefined) { // metadataFileFormat was added in 2.2.21
|
|
// All users using old settings will stay abs until changed
|
|
this.metadataFileFormat = 'abs'
|
|
}
|
|
|
|
// As of v2.4.5 only json is supported
|
|
if (this.metadataFileFormat !== 'json') {
|
|
Logger.warn(`[ServerSettings] Invalid metadataFileFormat ${this.metadataFileFormat} (as of v2.4.5 only json is supported)`)
|
|
this.metadataFileFormat = 'json'
|
|
}
|
|
|
|
if (this.logLevel !== Logger.logLevel) {
|
|
Logger.setLogLevel(this.logLevel)
|
|
}
|
|
}
|
|
|
|
toJSON() { // Use toJSONForBrowser if sending to client
|
|
return {
|
|
id: this.id,
|
|
tokenSecret: this.tokenSecret, // Do not return to client
|
|
scannerFindCovers: this.scannerFindCovers,
|
|
scannerCoverProvider: this.scannerCoverProvider,
|
|
scannerParseSubtitle: this.scannerParseSubtitle,
|
|
scannerPreferMatchedMetadata: this.scannerPreferMatchedMetadata,
|
|
scannerDisableWatcher: this.scannerDisableWatcher,
|
|
storeCoverWithItem: this.storeCoverWithItem,
|
|
storeMetadataWithItem: this.storeMetadataWithItem,
|
|
metadataFileFormat: this.metadataFileFormat,
|
|
rateLimitLoginRequests: this.rateLimitLoginRequests,
|
|
rateLimitLoginWindow: this.rateLimitLoginWindow,
|
|
backupSchedule: this.backupSchedule,
|
|
backupsToKeep: this.backupsToKeep,
|
|
maxBackupSize: this.maxBackupSize,
|
|
loggerDailyLogsToKeep: this.loggerDailyLogsToKeep,
|
|
loggerScannerLogsToKeep: this.loggerScannerLogsToKeep,
|
|
homeBookshelfView: this.homeBookshelfView,
|
|
bookshelfView: this.bookshelfView,
|
|
podcastEpisodeSchedule: this.podcastEpisodeSchedule,
|
|
sortingIgnorePrefix: this.sortingIgnorePrefix,
|
|
sortingPrefixes: [...this.sortingPrefixes],
|
|
chromecastEnabled: this.chromecastEnabled,
|
|
dateFormat: this.dateFormat,
|
|
timeFormat: this.timeFormat,
|
|
language: this.language,
|
|
logLevel: this.logLevel,
|
|
version: this.version,
|
|
buildNumber: this.buildNumber,
|
|
authLoginCustomMessage: this.authLoginCustomMessage,
|
|
authActiveAuthMethods: this.authActiveAuthMethods,
|
|
authOpenIDIssuerURL: this.authOpenIDIssuerURL,
|
|
authOpenIDAuthorizationURL: this.authOpenIDAuthorizationURL,
|
|
authOpenIDTokenURL: this.authOpenIDTokenURL,
|
|
authOpenIDUserInfoURL: this.authOpenIDUserInfoURL,
|
|
authOpenIDJwksURL: this.authOpenIDJwksURL,
|
|
authOpenIDLogoutURL: this.authOpenIDLogoutURL,
|
|
authOpenIDClientID: this.authOpenIDClientID, // Do not return to client
|
|
authOpenIDClientSecret: this.authOpenIDClientSecret, // Do not return to client
|
|
authOpenIDButtonText: this.authOpenIDButtonText,
|
|
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
|
|
authOpenIDAutoRegister: this.authOpenIDAutoRegister,
|
|
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
|
|
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs // Do not return to client
|
|
}
|
|
}
|
|
|
|
toJSONForBrowser() {
|
|
const json = this.toJSON()
|
|
delete json.tokenSecret
|
|
delete json.authOpenIDClientID
|
|
delete json.authOpenIDClientSecret
|
|
delete json.authOpenIDMobileRedirectURIs
|
|
return json
|
|
}
|
|
|
|
get supportedAuthMethods() {
|
|
return ['local', 'openid']
|
|
}
|
|
|
|
/**
|
|
* Auth settings required for openid to be valid
|
|
*/
|
|
get isOpenIDAuthSettingsValid() {
|
|
return this.authOpenIDIssuerURL &&
|
|
this.authOpenIDAuthorizationURL &&
|
|
this.authOpenIDTokenURL &&
|
|
this.authOpenIDUserInfoURL &&
|
|
this.authOpenIDJwksURL &&
|
|
this.authOpenIDClientID &&
|
|
this.authOpenIDClientSecret
|
|
}
|
|
|
|
get authenticationSettings() {
|
|
return {
|
|
authLoginCustomMessage: this.authLoginCustomMessage,
|
|
authActiveAuthMethods: this.authActiveAuthMethods,
|
|
authOpenIDIssuerURL: this.authOpenIDIssuerURL,
|
|
authOpenIDAuthorizationURL: this.authOpenIDAuthorizationURL,
|
|
authOpenIDTokenURL: this.authOpenIDTokenURL,
|
|
authOpenIDUserInfoURL: this.authOpenIDUserInfoURL,
|
|
authOpenIDJwksURL: this.authOpenIDJwksURL,
|
|
authOpenIDLogoutURL: this.authOpenIDLogoutURL,
|
|
authOpenIDClientID: this.authOpenIDClientID, // Do not return to client
|
|
authOpenIDClientSecret: this.authOpenIDClientSecret, // Do not return to client
|
|
authOpenIDButtonText: this.authOpenIDButtonText,
|
|
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
|
|
authOpenIDAutoRegister: this.authOpenIDAutoRegister,
|
|
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
|
|
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs // Do not return to client
|
|
}
|
|
}
|
|
|
|
get authFormData() {
|
|
const clientFormData = {
|
|
authLoginCustomMessage: this.authLoginCustomMessage
|
|
}
|
|
if (this.authActiveAuthMethods.includes('openid')) {
|
|
clientFormData.authOpenIDButtonText = this.authOpenIDButtonText
|
|
clientFormData.authOpenIDAutoLaunch = this.authOpenIDAutoLaunch
|
|
}
|
|
return clientFormData
|
|
}
|
|
|
|
/**
|
|
* Update server settings
|
|
*
|
|
* @param {Object} payload
|
|
* @returns {boolean} true if updates were made
|
|
*/
|
|
update(payload) {
|
|
let hasUpdates = false
|
|
for (const key in payload) {
|
|
if (key === 'sortingPrefixes') {
|
|
// Sorting prefixes are updated with the /api/sorting-prefixes endpoint
|
|
continue
|
|
} else if (key === 'authActiveAuthMethods') {
|
|
if (!payload[key]?.length) {
|
|
Logger.error(`[ServerSettings] Invalid authActiveAuthMethods`, payload[key])
|
|
continue
|
|
}
|
|
this.authActiveAuthMethods.sort()
|
|
payload[key].sort()
|
|
if (payload[key].join() !== this.authActiveAuthMethods.join()) {
|
|
this.authActiveAuthMethods = payload[key]
|
|
hasUpdates = true
|
|
}
|
|
} else if (this[key] !== payload[key]) {
|
|
if (key === 'logLevel') {
|
|
Logger.setLogLevel(payload[key])
|
|
}
|
|
this[key] = payload[key]
|
|
hasUpdates = true
|
|
}
|
|
}
|
|
return hasUpdates
|
|
}
|
|
}
|
|
module.exports = ServerSettings
|