From 9e45c3e13522538d3f6e8d3deabdb8c6eeccf52e Mon Sep 17 00:00:00 2001 From: Rostislav Raykov Date: Sat, 1 Nov 2025 17:23:51 +0100 Subject: [PATCH 1/6] chore: Add undici@5 for Node 18 compatibility --- package-lock.json | 23 ++++++++++++++++------- package.json | 1 + 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index e46f67bc..7ca17092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,6 +93,7 @@ "tsx": "^4.7.0", "typeson": "^9.0.4", "typeson-registry": "^11.1.1", + "undici": "^5.29.0", "vite-express": "^0.16.0", "vlc-client": "^1.1.1", "xml2js": "0.6.1", @@ -1387,6 +1388,14 @@ "npm": ">=9.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "engines": { + "node": ">=14" + } + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", @@ -11924,14 +11933,14 @@ "dev": true }, "node_modules/undici": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", - "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", - "license": "MIT", - "optional": true, - "peer": true, + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, "engines": { - "node": ">=20.18.1" + "node": ">=14.0" } }, "node_modules/undici-types": { diff --git a/package.json b/package.json index a933596c..ec9d3f03 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "tsx": "^4.7.0", "typeson": "^9.0.4", "typeson-registry": "^11.1.1", + "undici": "^5.29.0", "vite-express": "^0.16.0", "vlc-client": "^1.1.1", "xml2js": "0.6.1", From 38cb6364861a2c86d548791d69742cba03484a65 Mon Sep 17 00:00:00 2001 From: Rostislav Raykov Date: Sat, 1 Nov 2025 17:23:51 +0100 Subject: [PATCH 2/6] feat(api): Add pagination support to Last.fm and Listenbrainz API clients --- .../typings/lastfm-node-client.d.ts | 10 ++++++ src/backend/common/vendor/LastfmApiClient.ts | 22 ++++++++++++- .../common/vendor/ListenbrainzApiClient.ts | 33 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/backend/common/infrastructure/typings/lastfm-node-client.d.ts b/src/backend/common/infrastructure/typings/lastfm-node-client.d.ts index 8026e1eb..6da0058b 100644 --- a/src/backend/common/infrastructure/typings/lastfm-node-client.d.ts +++ b/src/backend/common/infrastructure/typings/lastfm-node-client.d.ts @@ -90,11 +90,21 @@ declare module 'lastfm-node-client' { user: string limit?: number extended?: boolean + page?: number + from?: number + to?: number } export interface UserGetRecentTracksResponse { recenttracks: { track: TrackObject[] + '@attr'?: { + user: string + totalPages: string + page: string + perPage: string + total: string + } } } diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index 3c27da0f..2db15a04 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -3,7 +3,8 @@ import LastFm, { AuthGetSessionResponse, LastfmTrackUpdateRequest, TrackObject, TrackScrobblePayload, - UserGetInfoResponse + UserGetInfoResponse, + UserGetRecentTracksResponse } from "lastfm-node-client"; import { PlayObject } from "../../../core/Atomic.js"; import { nonEmptyStringOrDefault, splitByFirstFound } from "../../../core/StringUtils.js"; @@ -204,6 +205,25 @@ export default class LastfmApiClient extends AbstractApiClient { throw e; } } + + getRecentTracksWithPagination = async (options: { + page?: number; + limit?: number; + from?: number; + to?: number; + } = {}) => { + const { page = 1, limit = 200, from, to } = options; + + return await this.callApi((client: any) => client.userGetRecentTracks({ + user: this.user, + sk: this.client.sessionKey, + limit, + page, + from, + to, + extended: true + })); + } } export const scrobblePayloadToPlay = (obj: LastfmTrackUpdateRequest): PlayObject => { diff --git a/src/backend/common/vendor/ListenbrainzApiClient.ts b/src/backend/common/vendor/ListenbrainzApiClient.ts index 14e13fa9..0a3e7dca 100644 --- a/src/backend/common/vendor/ListenbrainzApiClient.ts +++ b/src/backend/common/vendor/ListenbrainzApiClient.ts @@ -263,6 +263,39 @@ export class ListenbrainzApiClient extends AbstractApiClient { return playObj; } + getUserListensWithPagination = async (options: { + count?: number; + minTs?: number; + maxTs?: number; + user?: string; + } = {}): Promise => { + const { count = 100, minTs, maxTs, user } = options; + + try { + const query: any = { count }; + if (minTs !== undefined) { + query.min_ts = minTs; + } + if (maxTs !== undefined) { + query.max_ts = maxTs; + } + + const resp = await this.callApi(request + .get(`${joinedUrl(this.url.url,'1/user', user ?? this.config.username, 'listens')}`) + .timeout({ + response: 15000, + deadline: 30000 + }) + .query(query)); + + const {body: {payload}} = resp as any; + return payload as ListensResponse; + } catch (e) { + throw e; + } + } + + static formatPlayObj(obj: any, options: FormatPlayObjectOptions): PlayObject { return listenResponseToPlay(obj); } From 1ab625ae234f3edc70fd5d403ea87a47e3558bdd Mon Sep 17 00:00:00 2001 From: Rostislav Raykov Date: Sat, 1 Nov 2025 17:23:51 +0100 Subject: [PATCH 3/6] feat(transfer): Add transfer/backfill system for copying scrobble history --- docsite/static/aio.json | 2 +- docsite/static/source.json | 2 +- src/backend/common/schema/aio-source.json | 2 +- src/backend/common/schema/aio.json | 2 +- src/backend/common/schema/source.json | 2 +- src/backend/index.ts | 5 +- .../scrobblers/AbstractScrobbleClient.ts | 21 +- src/backend/server/api.ts | 47 +- src/backend/server/index.ts | 5 +- src/backend/transfer/TransferJob.ts | 402 ++++++++++++++++++ src/backend/transfer/TransferManager.ts | 196 +++++++++ src/client/App.tsx | 8 + src/client/store.ts | 4 +- src/client/transfer/TransferPage.tsx | 222 ++++++++++ src/client/transfer/TransferStatus.tsx | 192 +++++++++ src/client/transfer/transferApi.ts | 80 ++++ 16 files changed, 1180 insertions(+), 12 deletions(-) create mode 100644 src/backend/transfer/TransferJob.ts create mode 100644 src/backend/transfer/TransferManager.ts create mode 100644 src/client/transfer/TransferPage.tsx create mode 100644 src/client/transfer/TransferStatus.tsx create mode 100644 src/client/transfer/transferApi.ts diff --git a/docsite/static/aio.json b/docsite/static/aio.json index 36b2296b..4e75f1de 100644 --- a/docsite/static/aio.json +++ b/docsite/static/aio.json @@ -1 +1 @@ -{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"sourceDefaults":{"$ref":"#/definitions/SourceDefaults"},"clientDefaults":{"$ref":"#/definitions/ClientDefaults"},"sources":{"type":"array","items":{"$ref":"#/definitions/SourceAIOConfig"}},"clients":{"type":"array","items":{"$ref":"#/definitions/ClientAIOConfig"}},"webhooks":{"type":"array","items":{"$ref":"#/definitions/WebhookConfig"}},"port":{"type":"number","description":"Set the port the multi-scrobbler UI will be served from","default":9078,"examples":[9078]},"baseUrl":{"type":"string","description":"Set the Base URL the application should assume the UI is served from.\n\nThis will affect how default redirect URLs are generated (spotify, lastfm, deezer) and some logging messages.\n\nIt will NOT set the actual interface/IP that the application is listening on.\n\nThis can also be set using the BASE_URL environmental variable.","default":"http://localhost","examples":["http://localhost","http://192.168.0.101","https://ms.myDomain.tld"]},"logging":{"$ref":"#/definitions/LogOptions"},"disableWeb":{"type":"boolean","description":"Disable web server from running/listening on port.\n\nThis will also make any ingress sources (Plex, Jellyfin, Tautulli, etc...) unusable"},"debugMode":{"type":"boolean","description":"Enables ALL relevant logging and debug options for all sources/clients, when none are defined.\n\nThis is a convenience shortcut for enabling all output needed to troubleshoot an issue and does not need to be on for normal operation.\n\nIt can also be enabled with the environmental variable DEBUG_MODE=true","default":false,"examples":[false]},"cache":{"$ref":"#/definitions/CacheConfigOptions"}},"definitions":{"SourceDefaults":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"SourceDefaults"},"LogLevel":{"type":"string","enum":["silent","fatal","error","warn","info","log","verbose","debug"],"description":"Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.","title":"LogLevel"},"FileLogOptions":{"type":"object","properties":{"timestamp":{"type":"string","enum":["unix","iso","auto"],"description":"For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime","default":"auto"},"size":{"type":["number","string"],"description":"The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.","default":"10MB"},"frequency":{"anyOf":[{"type":"string","enum":["daily"]},{"type":"string","enum":["hourly"]},{"type":"number"}],"description":"The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.","default":"daily"},"path":{"anyOf":[{"type":"string"},{"$comment":"() => string"}],"description":"The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD","default":"./logs/app.log"},"level":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false}],"description":"Specify the minimum log level to output to rotating files. If `false` no log files will be created."}},"title":"FileLogOptions"},"ScrobbleThresholds":{"type":"object","properties":{"duration":{"type":["number","null"],"description":"The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.","default":240,"examples":[240]},"percent":{"type":["number","null"],"description":"The percentage (as an integer) of a track that should have been seen played before it should be scrobbled. Only used if the Source provides information about how long the track is.\n\nSet to null to disable.\n\nNOTE: This should be used with care when the Source is a \"polling\" type (has an 'interval' property). If the track is short and the interval is too high MS may ignore the track if percentage is high because it had not \"seen\" the track for long enough from first discovery, even if you have been playing the track for longer.","default":50,"examples":[50]}},"title":"ScrobbleThresholds"},"PlayTransformOptions":{"type":"object","properties":{"log":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["all"]}]},"preCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"compare":{"type":"object","properties":{"candidate":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"existing":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}}},"postCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}},"title":"PlayTransformOptions"},"PlayTransformPartsConfig":{"anyOf":[{"$ref":"#/definitions/PlayTransformPartsArray%3CSearchAndReplaceTerm%3E"},{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"}],"title":"PlayTransformPartsConfig"},"PlayTransformPartsArray":{"type":"array","items":{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"},"title":"PlayTransformPartsArray"},"PlayTransformParts":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"title":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"artists":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"album":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}}},"title":"PlayTransformParts"},"WhenConditionsConfig":{"$ref":"#/definitions/WhenConditions%3Cstring%3E","title":"WhenConditionsConfig"},"WhenConditions":{"type":"array","items":{"$ref":"#/definitions/WhenParts%3Cstring%3E"},"title":"WhenConditions"},"WhenParts":{"$ref":"#/definitions/PlayTransformPartsAtomic%3Cstring%3E","title":"WhenParts"},"PlayTransformPartsAtomic":{"type":"object","properties":{"title":{"type":"string"},"artists":{"type":"string"},"album":{"type":"string"}},"title":"PlayTransformPartsAtomic"},"SearchAndReplaceTerm":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/ConditionalSearchAndReplaceTerm"}],"title":"SearchAndReplaceTerm"},"ConditionalSearchAndReplaceTerm":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"search":{},"replace":{}},"required":["search","replace"],"title":"ConditionalSearchAndReplaceTerm"},"ClientDefaults":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"ClientDefaults"},"MatchLoggingOptions":{"type":"object","properties":{"onNoMatch":{"type":"boolean","description":"Log to DEBUG when a new track does NOT match an existing scrobble","default":false,"examples":[false]},"onMatch":{"type":"boolean","description":"Log to DEBUG when a new track DOES match an existing scrobble","default":false,"examples":[false]},"confidenceBreakdown":{"type":"boolean","description":"Include confidence breakdowns in track match logging, if applicable","default":false,"examples":[false]}},"description":"Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.","title":"MatchLoggingOptions"},"SourceAIOConfig":{"anyOf":[{"$ref":"#/definitions/SpotifySourceAIOConfig"},{"$ref":"#/definitions/PlexSourceAIOConfig"},{"$ref":"#/definitions/PlexApiSourceAIOConfig"},{"$ref":"#/definitions/TautulliSourceAIOConfig"},{"$ref":"#/definitions/DeezerSourceAIOConfig"},{"$ref":"#/definitions/DeezerInternalAIOConfig"},{"$ref":"#/definitions/ListenbrainzEndpointSourceAIOConfig"},{"$ref":"#/definitions/LastFMEndpointSourceAIOConfig"},{"$ref":"#/definitions/SubsonicSourceAIOConfig"},{"$ref":"#/definitions/JellySourceAIOConfig"},{"$ref":"#/definitions/JellyApiSourceAIOConfig"},{"$ref":"#/definitions/LastFmSouceAIOConfig"},{"$ref":"#/definitions/YTMusicSourceAIOConfig"},{"$ref":"#/definitions/MPRISSourceAIOConfig"},{"$ref":"#/definitions/MopidySourceAIOConfig"},{"$ref":"#/definitions/ListenBrainzSourceAIOConfig"},{"$ref":"#/definitions/JRiverSourceAIOConfig"},{"$ref":"#/definitions/KodiSourceAIOConfig"},{"$ref":"#/definitions/WebScrobblerSourceAIOConfig"},{"$ref":"#/definitions/ChromecastSourceAIOConfig"},{"$ref":"#/definitions/MalojaSourceAIOConfig"},{"$ref":"#/definitions/MusikcubeSourceAIOConfig"},{"$ref":"#/definitions/MusicCastSourceAIOConfig"},{"$ref":"#/definitions/MPDSourceAIOConfig"},{"$ref":"#/definitions/VLCSourceAIOConfig"},{"$ref":"#/definitions/IcecastSourceAIOConfig"},{"$ref":"#/definitions/AzuracastSourceAIOConfig"},{"$ref":"#/definitions/KoitoSourceAIOConfig"},{"$ref":"#/definitions/TealSourceAIOConfig"},{"$ref":"#/definitions/RockskySourceAIOConfig"}],"title":"SourceAIOConfig"},"SpotifySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SpotifySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["spotify"]}},"required":["data","type"],"title":"SpotifySourceAIOConfig"},"SpotifySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)\n\nIt is unlikely you should need to change this unless you scrobble many very short tracks often\n\nReading:\n* https://developer.spotify.com/documentation/web-api/guides/rate-limits/\n* https://medium.com/mendix/limiting-your-amount-of-calls-in-mendix-most-of-the-time-rest-835dde55b10e\n * Rate limit may ~180 req/min\n* https://community.spotify.com/t5/Spotify-for-Developers/Web-API-ratelimit/m-p/5503150/highlight/true#M7930\n * Informally indicated as 20 req/sec? Probably for burstiness","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"spotify client id","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"clientSecret":{"type":"string","description":"spotify client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"spotify redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/callback","examples":["http://localhost:9078/callback"]}},"required":["clientId","clientSecret"],"title":"SpotifySourceData"},"CommonSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonSourceOptions"},"PlexSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","type"],"title":"PlexSourceAIOConfig"},"PlexSourceData":{"type":"object","properties":{"user":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"libraries":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of libraries to scrobble tracks from\n\nIf none are provided tracks from all libraries will be scrobbled","examples":[["Audio","Music"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName"]]}},"title":"PlexSourceData"},"PlexApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/PlexApiOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","options","type"],"title":"PlexApiSourceAIOConfig"},"PlexApiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"token":{"type":"string"},"url":{"type":"string","description":"http(s)://HOST:PORT of the Plex server to connect to"},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"}},"required":["url"],"title":"PlexApiData"},"PlexApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"ignoreInvalidCert":{"type":"boolean","description":"Ignore invalid cert errors when connecting to Plex\n\nUseful for Plex servers using \"Required\" Secure Connections with self-signed certificates\n\nDo not enable unless you know you need this.","default":false}},"title":"PlexApiOptions"},"TautulliSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["tautulli"]}},"required":["data","type"],"title":"TautulliSourceAIOConfig"},"DeezerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerSourceAIOConfig"},"DeezerData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"deezer client id","examples":["a89cba1569901a0671d5a9875fed4be1"]},"clientSecret":{"type":"string","description":"deezer client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/deezer/callback","examples":["http://localhost:9078/deezer/callback"]}},"required":["clientId","clientSecret"],"title":"DeezerData"},"DeezerInternalAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerInternalData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/DeezerInternalSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerInternalAIOConfig"},"DeezerInternalData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"arl":{"type":"string","description":"ARL retrieved from Deezer response header"},"userAgent":{"type":"string","description":"User agent","default":"Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0"}},"required":["arl"],"title":"DeezerInternalData"},"DeezerInternalSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"fuzzyDiscoveryIgnore":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["aggressive"]}]}},"title":"DeezerInternalSourceOptions"},"ListenbrainzEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenbrainzEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlz"]}},"required":["type"],"title":"ListenbrainzEndpointSourceAIOConfig"},"ListenbrainzEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Listenbrainz endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/listenbrainz/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/listenbrainz/originb\n\nIf no slug is found from an extension's incoming webhook event the first Listenbrainz source without a slug will be used"},"token":{"type":["string","null"],"description":"If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source\n\nSee: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#add-the-user-token-to-your-requests"}},"title":"ListenbrainzEndpointData"},"LastFMEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFMEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlfm"]}},"required":["type"],"title":"LastFMEndpointSourceAIOConfig"},"LastFMEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Last.fm endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/lastfm/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/lastfm/originb\n\nIf no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used"}},"title":"LastFMEndpointData"},"SubsonicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SubsonicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["subsonic"]}},"required":["data","type"],"title":"SubsonicSourceAIOConfig"},"SubsonicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the subsonic media server to query","examples":["http://airsonic.local"]},"user":{"type":"string","description":"Username to login to the server with","examples":[["MyUser"]]},"password":{"type":"string","description":"Password for the user to login to the server with","examples":["MyPassword"]},"ignoreTlsErrors":{"type":"boolean","description":"If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection\n\nWARNING: This should be used with caution as your traffic may not be encrypted.","default":false},"legacyAuthentication":{"type":"boolean","description":"Older Subsonic versions, and some badly implemented servers (Nextcloud), use legacy authentication which sends your password in CLEAR TEXT. This is less secure than the newer, recommended hashing authentication method but in some cases it is needed. See \"Authentication\" section here => https://www.subsonic.org/pages/api.jsp\n\nIf this option is not specified it will be turned on if the subsonic server responds with error code 41 \"Token authentication not supported for LDAP users.\" -- See Error Handling section => https://www.subsonic.org/pages/api.jsp","default":false},"usersAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf undefined or an empty string/list MS will scrobble activity from all users"}},"required":["url","user","password"],"title":"SubsonicData"},"JellySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","type"],"title":"JellySourceAIOConfig"},"JellyData":{"type":"object","properties":{"users":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName1"]]}},"title":"JellyData"},"JellyApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/JellyApiOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","options","type"],"title":"JellyApiSourceAIOConfig"},"JellyApiData":{"type":"object","properties":{"url":{"type":"string","description":"HOST:PORT of the Jellyfin server to connect to"},"user":{"type":"string","description":"The username of the user to authenticate for or track scrobbles for"},"password":{"type":"string","description":"Password of the username to authenticate for\n\nRequired if `apiKey` is not provided."},"apiKey":{"type":"string","description":"API Key to authenticate with.\n\nRequired if `password` is not provided."},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"},"additionalAllowedLibraryTypes":{"type":"array","items":{},"description":"Allow MS to scrobble audio media in libraries classified other than 'music'\n\n`librariesAllow` will achieve the same result as this but this is more convenient if you do not want to explicitly list every library name or are only using `librariesBlock`"},"allowUnknown":{"type":"boolean","description":"Force media with a type of \"Unknown\" to be counted as Audio","default":false},"frontendUrlOverride":{"type":"string","description":"HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from)\nIf unspecified it will use the normal server HOST and PORT from the `url`\nNecessary if you are using a reverse proxy or other network configuration that prevents the frontend from accessing the server directly\n\nENV: JELLYFIN_FRONTEND_URL_OVERRIDE"}},"required":["url","user"],"title":"JellyApiData"},"JellyApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"JellyApiOptions"},"LastFmSouceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFmSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["lastfm"]}},"required":["data","type"],"title":"LastFmSouceAIOConfig"},"LastFmSourceData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]},"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"}},"required":["apiKey","secret"],"title":"LastFmSourceData"},"YTMusicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/YTMusicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"type":"object","properties":{"logAuth":{"type":"boolean","description":"When true MS will log to DEBUG all of the credentials data it receives from YTM"},"logDiff":{"type":"boolean","description":"Always log history diff\n\nBy default MS will log to `WARN` if history diff is inconsistent but does not log if diff is expected (on new tracks found)\nSet this to `true` to ALWAYS log diff on new tracks. Expected diffs will log to `DEBUG` and inconsistent diffs will continue to log to `WARN`","default":false},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}}},"type":{"type":"string","enum":["ytmusic"]}},"required":["type"],"title":"YTMusicSourceAIOConfig"},"YTMusicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"cookie":{"type":"string","description":"The cookie retrieved from the Request Headers of music.youtube.com after logging in.\n\nSee https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers for how to retrieve this value.","examples":["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]},"clientId":{"type":"string","description":"Google Cloud Console project OAuth Client ID\n\nGenerated from a custom OAuth Client, see docs"},"clientSecret":{"type":"string","description":"Google Cloud Console project OAuth Client Secret\n\nGenerated from a custom OAuth Client, see docs"},"redirectUri":{"type":"string","description":"Google Cloud Console project OAuth Client Authorized redirect URI\n\nGenerated from a custom OAuth Client, see docs. multi-scrobbler will generate a default based on BASE_URL.\nOnly specify this if the default does not work for you."},"innertubeOptions":{"$ref":"#/definitions/InnertubeOptions","description":"Additional options for authorization and tailoring YTM client"}},"title":"YTMusicData"},"InnertubeOptions":{"type":"object","properties":{"po_token":{"type":"string","description":"Proof of Origin token\n\nMay be required if YTM starts returning 403"},"visitor_data":{"type":"string","description":"Visitor ID value found in VISITOR_INFO1_LIVE or visitorData cookie\n\nMay be required if YTM starts returning 403"},"account_index":{"type":"number","description":"If account login results in being able to choose multiple account, use a zero-based index to choose which one to monitor","examples":[0,1]},"location":{"type":"string"},"lang":{"type":"string"},"generate_session_locally":{"type":"boolean"},"device_category":{"type":"string"},"client_type":{"type":"string"},"timezone":{"type":"string"}},"title":"InnertubeOptions"},"MPRISSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPRISData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mpris"]}},"required":["data","type"],"title":"MPRISSourceAIOConfig"},"MPRISData":{"type":"object","properties":{"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any players that START WITH these values, case-insensitive","examples":[["spotify","vlc"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","vlc"]]}},"title":"MPRISData"},"MopidySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MopidyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mopidy"]}},"required":["data","type"],"title":"MopidySourceAIOConfig"},"MopidyData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Mopidy HTTP server to connect to\n\nYou MUST have Mopidy-HTTP extension enabled: https://mopidy.com/ext/http\n\nmulti-scrobbler connects to the WebSocket endpoint that ultimately looks like this => `ws://localhost:6680/mopidy/ws/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `6680`\n* Path => `/mopidy/ws/`","examples":["ws://localhost:6680/mopidy/ws/"],"default":"ws://localhost:6680/mopidy/ws/"},"uriBlacklist":{"type":"array","items":{"type":"string"},"description":"Do not scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Don't scrobble tracks from soundcloud by adding 'soundcloud' to this list.\n\nList is ignored if uriWhitelist is used."},"uriWhitelist":{"type":"array","items":{"type":"string"},"description":"Only scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Only scrobble tracks from soundcloud by adding 'soundcloud' to this list."},"albumBlacklist":{"type":"array","items":{"type":"string"},"description":"Remove album data that matches any case-insensitive string from this list when scrobbling,\n\nFor certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use \"Soundcloud\" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake name to this list. Multi-scrobbler will still scrobble the track, just without the bad data.","examples":[["Soundcloud","Mixcloud"]],"default":["Soundcloud"]}},"title":"MopidyData"},"ListenBrainzSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenBrainzSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `listenbrainz.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["listenbrainz"]}},"required":["data","type"],"title":"ListenBrainzSourceAIOConfig"},"ListenBrainzSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzSourceData"},"JRiverSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JRiverData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jriver"]}},"required":["data","type"],"title":"JRiverSourceAIOConfig"},"JRiverData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the JRiver HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:52199/MCWS/v1/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `52199`\n* Path => `/MCWS/v1/`","examples":["http://localhost:52199/MCWS/v1/"],"default":"http://localhost:52199/MCWS/v1/"},"username":{"type":"string","description":"If you have enabled authentication, the username you set"},"password":{"type":"string","description":"If you have enabled authentication, the password you set"}},"required":["url"],"title":"JRiverData"},"KodiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KodiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["kodi"]}},"required":["data","type"],"title":"KodiSourceAIOConfig"},"KodiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Kodi HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:8080/jsonrpc`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `8080`\n* Path => `/jsonrpc`","examples":["http://localhost:8080/jsonrpc"],"default":"http://localhost:8080/jsonrpc"},"username":{"type":"string","description":"The username set for Remote Control via Web Sever"},"password":{"type":"string","description":"The password set for Remote Control via Web Sever"}},"required":["url","username","password"],"title":"KodiData"},"WebScrobblerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/WebScrobblerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["webscrobbler"]}},"required":["type"],"title":"WebScrobblerSourceAIOConfig"},"WebScrobblerData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIn WebScrobbler's Webhook you must set an 'API URL'. All MS WebScrobbler sources must start like:\n\nhttp://localhost:9078/api/webscrobbler\n\nIf you are using multiple WebScrobbler sources (scrobbles for many users) you must use a slug to match Sources with each users extension.\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/webscrobbler/usera\n* slug: 'userb' => API URL: http://localhost:9078/api/webscrobbler/userb\n\nIf no slug is found from an extension's incoming webhook event the first WebScrobbler source without a slug will be used"},"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Block scrobbling from specific WebScrobbler Connectors","examples":[["youtube"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only allow scrobbling from specific WebScrobbler Connectors","examples":[["mixcloud","soundcloud","bandcamp"]]}},"title":"WebScrobblerData"},"ChromecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ChromecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["chromecast"]}},"required":["data","type"],"title":"ChromecastSourceAIOConfig"},"ChromecastData":{"type":"object","properties":{"blacklistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"whitelistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any cast device that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"blacklistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any application that START WITH these values, case-insensitive","examples":[["spotify","pandora"]]},"whitelistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any application that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","pandora"]]},"useAvahi":{"type":"boolean","description":"Try to use Avahi and avahi-browse to resolve mDNS devices instead of native mDNS querying\n\nUseful for docker (alpine) container where mDNS resolution is not yet supported. Avahi socket must be exposed to the container and avahi-tools must be installed.","default":false},"useAutoDiscovery":{"type":"boolean","description":"Use mDNS to discovery Google Cast devices on your next automatically?\n\nIf not explicitly set then it is TRUE if `devices` is not set"},"devices":{"type":"array","items":{"$ref":"#/definitions/ChromecastDeviceInfo"},"description":"A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set"},"allowUnknownMedia":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Chromecast Apps report a \"media type\" in the status info returned for whatever is currently playing\n\n* If set to TRUE then Music AND Generic/Unknown media will be tracked for ALL APPS\n* If set to FALSE then only media explicitly typed as Music will be tracked for ALL APPS\n* If set to a list then only Apps whose name contain one of these values, case-insensitive, will have Music AND Generic/Unknown tracked\n\nSee https://developers.google.com/cast/docs/media/messages#MediaInformation \"metadata\" property","default":false},"forceMediaRecognitionOn":{"type":"array","items":{"type":"string"},"description":"Media provided by any App whose name is listed here will ALWAYS be tracked, regardless of the \"media type\" reported\n\nApps will be recognized if they CONTAIN any of these values, case-insensitive"}},"title":"ChromecastData"},"ChromecastDeviceInfo":{"type":"object","properties":{"name":{"type":"string","description":"A friendly name to identify this device","examples":["MySmartTV"]},"address":{"type":"string","description":"The IP address of the device","examples":["192.168.0.115"]}},"required":["name","address"],"title":"ChromecastDeviceInfo"},"MalojaSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MalojaSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `maloja.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["maloja"]}},"required":["data","type"],"title":"MalojaSourceAIOConfig"},"MalojaSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaSourceData"},"MusikcubeSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusikcubeData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musikcube"]}},"required":["data","type"],"title":"MusikcubeSourceAIOConfig"},"MusikcubeData":{"type":"object","properties":{"url":{"type":"string","description":"URL of the Musikcube Websocket (Metadata) server to connect to\n\nYou MUST have enabled 'metadata' server and set a password: https://github.com/clangen/musikcube/wiki/remote-api-documentation\n * musikcube -> settings -> server setup\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `7905`","examples":["ws://localhost:7905"],"default":"ws://localhost:7905"},"password":{"type":"string","description":"Password set in Musikcube https://github.com/clangen/musikcube/wiki/remote-api-documentation\n\n* musikcube -> settings -> server setup -> password"},"device_id":{"type":"string"}},"required":["password"],"title":"MusikcubeData"},"MusicCastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusicCastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musiccast"]}},"required":["data","type"],"title":"MusicCastSourceAIOConfig"},"MusicCastData":{"type":"object","properties":{"url":{"type":"string","description":"The host or URL of the YamahaExtendedControl endpoint to use","examples":[["192.168.0.101","http://192.168.0.101/YamahaExtendedControl"]]}},"required":["url"],"title":"MusicCastData"},"MPDSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPDData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/MPDSourceOptions"},"type":{"type":"string","enum":["mpd"]}},"required":["data","options","type"],"title":"MPDSourceAIOConfig"},"MPDData":{"type":"object","properties":{"url":{"type":"string","description":"URL:PORT of the MPD server to connect to\n\nTo use this you must have TCP connections enabled for your MPD server https://mpd.readthedocs.io/en/stable/user.html#client-connections","examples":["localhost:6600"],"default":"localhost:6600"},"path":{"type":"string","description":"If using socket specify the path instead of url.\n\ntrailing `~` is replaced by your home directory"},"password":{"type":"string","description":"Password for the server, if set https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords"}},"title":"MPDData"},"MPDSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"MPDSourceOptions"},"VLCSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/VLCData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/VLCSourceOptions"},"type":{"type":"string","enum":["vlc"]}},"required":["data","type"],"title":"VLCSourceAIOConfig"},"VLCData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL:PORT of the VLC server to connect to\n\nTo use this you must have the Web (http) interface module enabled and a password set https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc","examples":["localhost:8080"],"default":"localhost:8080"},"password":{"type":"string","description":"Password for the server"}},"required":["password"],"title":"VLCData"},"VLCSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"filenamePatterns":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A list of regular expressions to use to extract metadata (title, album, artist) from a filename\n\nUsed when VLC reports only the filename for the current audio track"},"logFilenamePatterns":{"type":"boolean","description":"Log to DEBUG when a filename-only track is matched or not matched by filenamePatterns","default":false},"dumpVlcMetadata":{"type":"boolean","description":"Dump all the metadata VLC reports for an audio track to DEBUG.\n\nUse this if reporting an issue with VLC not correctly capturing metadata for a track.","default":false}},"title":"VLCSourceOptions"},"IcecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/IcecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/IcecastSourceOptions"},"type":{"type":"string","enum":["icecast"]}},"required":["data","type"],"title":"IcecastSourceAIOConfig"},"IcecastData":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/definitions/IcecastSource"}},"icestatsEndpoint":{"type":"string"},"statsEndpoint":{"type":"string"},"nextsongsEndpoint":{"type":"string"},"sevenhtmlEndpoint":{"type":"string"},"icyMetaInt":{"type":"number"},"url":{"type":"string","description":"The Icecast stream URL"}},"required":["url"],"title":"IcecastData"},"IcecastSource":{"type":"string","enum":["icy","ogg","icestats","stats","sevenhtml","nextsongs"],"title":"IcecastSource"},"IcecastSourceOptions":{"type":"object","properties":{"systemScrobble":{"type":"boolean","description":"For Sources that support manual listening, should MS default to scrobbling when no user interaction has occurred?\n\nIf not specified MS will use a Source's specific behavior, see Source's documentation."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"IcecastSourceOptions"},"AzuracastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/AzuracastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["azuracast"]}},"required":["data","type"],"title":"AzuracastSourceAIOConfig"},"AzuracastData":{"type":"object","properties":{"url":{"type":"string","description":"Base URL of the Azuracast instance\n\nThis does NOT include the station. If a station is included it will be ignored. Use `station` field to specify station, if necessary","examples":["https://radio.mydomain.tld","http://localhost:80"]},"station":{"type":"string","description":"The specific station to monitor\n\nScrobbling will only occur if any of the monitor conditions are met AND the station is ONLINE.\n\nTo monitor multiple stations create a Source for each station.","examples":["my-station-1"]},"monitorWhenListeners":{"type":["boolean","number"],"description":"Only activate scrobble monitoring if station\n\n* `true` => has any current listeners\n* `number` => has EQUAL TO or MORE THAN X number of listeners"},"monitorWhenLive":{"type":"boolean","description":"Only activate scrobble monitoring if station has a live DJ/Streamer","default":true},"apiKey":{"type":"string","description":"API Key used to access data about private streams\n\nhttps://www.azuracast.com/docs/developers/apis/#api-authentication"}},"required":["url","station"],"title":"AzuracastData"},"KoitoSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KoitoSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `koito.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["koito"]}},"required":["data","type"],"title":"KoitoSourceAIOConfig"},"KoitoSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoSourceData"},"TealSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/TealSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/TealSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"Should always be `souce` when using Tealfm as a Source","default":"source","examples":["source"]},"type":{"type":"string","enum":["tealfm"]}},"required":["data","type"],"title":"TealSourceAIOConfig"},"TealSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealSourceData"},"TealSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealSourceOptions"},"RockskySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/RockskySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/RockskySourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `rocksky.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["rocksky"]}},"required":["data","type"],"title":"RockskySourceAIOConfig"},"RockskySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockskySourceData"},"RockskySourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockskySourceOptions"},"ClientAIOConfig":{"anyOf":[{"$ref":"#/definitions/MalojaClientAIOConfig"},{"$ref":"#/definitions/LastfmClientAIOConfig"},{"$ref":"#/definitions/ListenBrainzClientAIOConfig"},{"$ref":"#/definitions/KoitoClientAIOConfig"},{"$ref":"#/definitions/TealClientAIOConfig"},{"$ref":"#/definitions/RockSkyClientAIOConfig"}],"title":"ClientAIOConfig"},"MalojaClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/MalojaClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Maloja as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["maloja"]}},"required":["data","name","type"],"title":"MalojaClientAIOConfig"},"MalojaClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaClientData"},"CommonClientOptions":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonClientOptions"},"LastfmClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/LastfmData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/LastfmClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using LastFM as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["lastfm"]}},"required":["data","name","type"],"title":"LastfmClientAIOConfig"},"LastfmData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]}},"required":["apiKey","secret"],"title":"LastfmData"},"LastfmClientOptions":{"type":"object","properties":{"nowPlaying":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Configure if this Client should report Now Playing from Sources that can scrobble to it\n\n* `true` (default) => Report Now Playing from any eligible Source. \n * If multiple Sources are Playing then reported Play is based on alphabetical order of Source names\n* `false` => Do not report Now Playing\n* `string` list => list of Source `names` that should be allowed to report Now Playing. Order of list determine priority of Play to Report.","default":true},"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"LastfmClientOptions"},"ListenBrainzClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/ListenBrainzClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Listenbrainz as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["listenbrainz"]}},"required":["data","name","type"],"title":"ListenBrainzClientAIOConfig"},"ListenBrainzClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzClientData"},"KoitoClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/KoitoClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Koito as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["koito"]}},"required":["data","name","type"],"title":"KoitoClientAIOConfig"},"KoitoClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoClientData"},"TealClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/TealClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/TealClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Tealfm as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["tealfm"]}},"required":["data","name","type"],"title":"TealClientAIOConfig"},"TealClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealClientData"},"TealClientOptions":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealClientOptions"},"RockSkyClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/RockSkyClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/RockSkyClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using RockSky as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["rocksky"]}},"required":["data","name","type"],"title":"RockSkyClientAIOConfig"},"RockSkyClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockSkyClientData"},"RockSkyClientOptions":{"type":"object","properties":{"nowPlaying":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Configure if this Client should report Now Playing from Sources that can scrobble to it\n\n* `true` (default) => Report Now Playing from any eligible Source. \n * If multiple Sources are Playing then reported Play is based on alphabetical order of Source names\n* `false` => Do not report Now Playing\n* `string` list => list of Source `names` that should be allowed to report Now Playing. Order of list determine priority of Play to Report.","default":true},"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockSkyClientOptions"},"WebhookConfig":{"anyOf":[{"$ref":"#/definitions/GotifyConfig"},{"$ref":"#/definitions/NtfyConfig"},{"$ref":"#/definitions/AppriseConfig"}],"title":"WebhookConfig"},"GotifyConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"url":{"type":"string","description":"The URL of the Gotify server. Same URL that would be used to reach the Gotify UI","examples":["http://192.168.0.100:8078"]},"token":{"type":"string","description":"The token created for this Application in Gotify","examples":["AQZI58fA.rfSZbm"]},"priorities":{"$ref":"#/definitions/PrioritiesConfig","description":"Priority of messages\n\n* Info -> 5\n* Warn -> 7\n* Error -> 10"}},"required":["token","type","url"],"title":"GotifyConfig"},"PrioritiesConfig":{"type":"object","properties":{"info":{"type":"number","examples":[5]},"warn":{"type":"number","examples":[7]},"error":{"type":"number","examples":[10]}},"required":["info","warn","error"],"title":"PrioritiesConfig"},"NtfyConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"url":{"type":"string","description":"The URL of the Ntfy server","examples":["http://192.168.0.100:8078"]},"topic":{"type":"string","description":"The topic mutli-scrobbler should POST to"},"username":{"type":"string","description":"Required if topic is protected"},"password":{"type":"string","description":"Required if topic is protected"},"token":{"type":"string","description":"Use instead of username/password, required if topic is protected"},"priorities":{"$ref":"#/definitions/PrioritiesConfig","description":"Priority of messages\n\n* Info -> 3\n* Warn -> 4\n* Error -> 5"}},"required":["topic","type","url"],"title":"NtfyConfig"},"AppriseConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"host":{"type":"string","description":"The URL of the apprise-api server","examples":["http://192.168.0.100:8078"]},"urls":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"If using [Stateless Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#stateless-solution) the Apprise config URL(s) to send"},"keys":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"If using [Persistent Store Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#persistent-storage-solution) the Configuration ID(s) to send to\n\nNote: If multiple keys are defined then MS will attempt to POST to each one individually"},"tags":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Optional [tag(s)](https://github.com/caronc/apprise-api?tab=readme-ov-file#tagging) to send in the notification payload"}},"required":["host","type"],"title":"AppriseConfig"},"LogOptions":{"type":"object","properties":{"level":{"$ref":"#/definitions/LogLevel","description":"Specify the minimum log level for all log outputs without their own level specified.\n\nDefaults to env `LOG_LEVEL` or `info` if not specified.","default":"info"},"file":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false},{"$ref":"#/definitions/FileLogOptions"}],"description":"Specify the minimum log level to output to rotating files or file output options. If `false` no log files will be created."},"console":{"$ref":"#/definitions/LogLevel","description":"Specify the minimum log level streamed to the console (or docker container)"}},"description":"Configure log levels and file options for an AppLogger.\n\n```ts\nconst infoLogger = loggerApp({\n level: 'info' // console and file will log any levels `info` and above\n});\n\nconst logger = loggerApp({\n console: 'debug', // console will log `debug` and higher\n file: 'warn' // file will log `warn` and higher\n});\n\nconst fileLogger = loggerRollingApp({\n console: 'debug', // console will log `debug` and higher\n file: {\n level: 'warn', // file will log `warn` and higher\n path: '/my/cool/path/output.log', // optionally, output to log file at this path\n frequency: 'hourly', // optionally, rotate hourly\n }\n});\n```","title":"LogOptions"},"CacheConfigOptions":{"type":"object","properties":{"metadata":{"$ref":"#/definitions/CacheMetadataConfig"},"scrobble":{"$ref":"#/definitions/CacheScrobbleConfig"},"auth":{"$ref":"#/definitions/CacheAuthConfig"}},"title":"CacheConfigOptions"},"CacheMetadataConfig":{"$ref":"#/definitions/CacheConfig%3CCacheMetadataProvider%3E","title":"CacheMetadataConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheMetadataProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheMetadataProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheMetadataProvider"},"CacheProvider":{"type":["string","boolean"],"enum":["memory","valkey","file",false],"title":"CacheProvider"},"CacheScrobbleConfig":{"$ref":"#/definitions/CacheConfig%3CCacheScrobbleProvider%3E","title":"CacheScrobbleConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheScrobbleProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheScrobbleProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheScrobbleProvider"},"CacheAuthConfig":{"$ref":"#/definitions/CacheConfig%3CCacheAuthProvider%3E","title":"CacheAuthConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheAuthProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheAuthProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheAuthProvider"}}} \ No newline at end of file +{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"sourceDefaults":{"$ref":"#/definitions/SourceDefaults"},"clientDefaults":{"$ref":"#/definitions/ClientDefaults"},"sources":{"type":"array","items":{"$ref":"#/definitions/SourceAIOConfig"}},"clients":{"type":"array","items":{"$ref":"#/definitions/ClientAIOConfig"}},"webhooks":{"type":"array","items":{"$ref":"#/definitions/WebhookConfig"}},"port":{"type":"number","description":"Set the port the multi-scrobbler UI will be served from","default":9078,"examples":[9078]},"baseUrl":{"type":"string","description":"Set the Base URL the application should assume the UI is served from.\n\nThis will affect how default redirect URLs are generated (spotify, lastfm, deezer) and some logging messages.\n\nIt will NOT set the actual interface/IP that the application is listening on.\n\nThis can also be set using the BASE_URL environmental variable.","default":"http://localhost","examples":["http://localhost","http://192.168.0.101","https://ms.myDomain.tld"]},"logging":{"$ref":"#/definitions/LogOptions"},"disableWeb":{"type":"boolean","description":"Disable web server from running/listening on port.\n\nThis will also make any ingress sources (Plex, Jellyfin, Tautulli, etc...) unusable"},"debugMode":{"type":"boolean","description":"Enables ALL relevant logging and debug options for all sources/clients, when none are defined.\n\nThis is a convenience shortcut for enabling all output needed to troubleshoot an issue and does not need to be on for normal operation.\n\nIt can also be enabled with the environmental variable DEBUG_MODE=true","default":false,"examples":[false]},"cache":{"$ref":"#/definitions/CacheConfigOptions"}},"definitions":{"SourceDefaults":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"SourceDefaults"},"LogLevel":{"type":"string","enum":["silent","fatal","error","warn","info","log","verbose","debug"],"description":"Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.","title":"LogLevel"},"FileLogOptions":{"type":"object","properties":{"timestamp":{"type":"string","enum":["unix","iso","auto"],"description":"For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime","default":"auto"},"size":{"type":["number","string"],"description":"The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.","default":"10MB"},"frequency":{"anyOf":[{"type":"string","enum":["daily"]},{"type":"string","enum":["hourly"]},{"type":"number"}],"description":"The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.","default":"daily"},"path":{"anyOf":[{"type":"string"},{"$comment":"() => string"}],"description":"The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD","default":"./logs/app.log"},"level":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false}],"description":"Specify the minimum log level to output to rotating files. If `false` no log files will be created."}},"title":"FileLogOptions"},"ScrobbleThresholds":{"type":"object","properties":{"duration":{"type":["number","null"],"description":"The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.","default":240,"examples":[240]},"percent":{"type":["number","null"],"description":"The percentage (as an integer) of a track that should have been seen played before it should be scrobbled. Only used if the Source provides information about how long the track is.\n\nSet to null to disable.\n\nNOTE: This should be used with care when the Source is a \"polling\" type (has an 'interval' property). If the track is short and the interval is too high MS may ignore the track if percentage is high because it had not \"seen\" the track for long enough from first discovery, even if you have been playing the track for longer.","default":50,"examples":[50]}},"title":"ScrobbleThresholds"},"PlayTransformOptions":{"type":"object","properties":{"log":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["all"]}]},"preCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"compare":{"type":"object","properties":{"candidate":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"existing":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}}},"postCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}},"title":"PlayTransformOptions"},"PlayTransformPartsConfig":{"anyOf":[{"$ref":"#/definitions/PlayTransformPartsArray%3CSearchAndReplaceTerm%3E"},{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"}],"title":"PlayTransformPartsConfig"},"PlayTransformPartsArray":{"type":"array","items":{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"},"title":"PlayTransformPartsArray"},"PlayTransformParts":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"title":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"artists":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"album":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}}},"title":"PlayTransformParts"},"WhenConditionsConfig":{"$ref":"#/definitions/WhenConditions%3Cstring%3E","title":"WhenConditionsConfig"},"WhenConditions":{"type":"array","items":{"$ref":"#/definitions/WhenParts%3Cstring%3E"},"title":"WhenConditions"},"WhenParts":{"$ref":"#/definitions/PlayTransformPartsAtomic%3Cstring%3E","title":"WhenParts"},"PlayTransformPartsAtomic":{"type":"object","properties":{"title":{"type":"string"},"artists":{"type":"string"},"album":{"type":"string"}},"title":"PlayTransformPartsAtomic"},"SearchAndReplaceTerm":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/ConditionalSearchAndReplaceTerm"}],"title":"SearchAndReplaceTerm"},"ConditionalSearchAndReplaceTerm":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"search":{},"replace":{}},"required":["search","replace"],"title":"ConditionalSearchAndReplaceTerm"},"ClientDefaults":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"ClientDefaults"},"MatchLoggingOptions":{"type":"object","properties":{"onNoMatch":{"type":"boolean","description":"Log to DEBUG when a new track does NOT match an existing scrobble","default":false,"examples":[false]},"onMatch":{"type":"boolean","description":"Log to DEBUG when a new track DOES match an existing scrobble","default":false,"examples":[false]},"confidenceBreakdown":{"type":"boolean","description":"Include confidence breakdowns in track match logging, if applicable","default":false,"examples":[false]}},"description":"Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.","title":"MatchLoggingOptions"},"SourceAIOConfig":{"anyOf":[{"$ref":"#/definitions/SpotifySourceAIOConfig"},{"$ref":"#/definitions/PlexSourceAIOConfig"},{"$ref":"#/definitions/PlexApiSourceAIOConfig"},{"$ref":"#/definitions/TautulliSourceAIOConfig"},{"$ref":"#/definitions/DeezerSourceAIOConfig"},{"$ref":"#/definitions/DeezerInternalAIOConfig"},{"$ref":"#/definitions/ListenbrainzEndpointSourceAIOConfig"},{"$ref":"#/definitions/LastFMEndpointSourceAIOConfig"},{"$ref":"#/definitions/SubsonicSourceAIOConfig"},{"$ref":"#/definitions/JellySourceAIOConfig"},{"$ref":"#/definitions/JellyApiSourceAIOConfig"},{"$ref":"#/definitions/LastFmSouceAIOConfig"},{"$ref":"#/definitions/YTMusicSourceAIOConfig"},{"$ref":"#/definitions/MPRISSourceAIOConfig"},{"$ref":"#/definitions/MopidySourceAIOConfig"},{"$ref":"#/definitions/ListenBrainzSourceAIOConfig"},{"$ref":"#/definitions/JRiverSourceAIOConfig"},{"$ref":"#/definitions/KodiSourceAIOConfig"},{"$ref":"#/definitions/WebScrobblerSourceAIOConfig"},{"$ref":"#/definitions/ChromecastSourceAIOConfig"},{"$ref":"#/definitions/MalojaSourceAIOConfig"},{"$ref":"#/definitions/MusikcubeSourceAIOConfig"},{"$ref":"#/definitions/MusicCastSourceAIOConfig"},{"$ref":"#/definitions/MPDSourceAIOConfig"},{"$ref":"#/definitions/VLCSourceAIOConfig"},{"$ref":"#/definitions/IcecastSourceAIOConfig"},{"$ref":"#/definitions/AzuracastSourceAIOConfig"},{"$ref":"#/definitions/KoitoSourceAIOConfig"},{"$ref":"#/definitions/TealSourceAIOConfig"},{"$ref":"#/definitions/RockskySourceAIOConfig"}],"title":"SourceAIOConfig"},"SpotifySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SpotifySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["spotify"]}},"required":["data","type"],"title":"SpotifySourceAIOConfig"},"SpotifySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)\n\nIt is unlikely you should need to change this unless you scrobble many very short tracks often\n\nReading:\n* https://developer.spotify.com/documentation/web-api/guides/rate-limits/\n* https://medium.com/mendix/limiting-your-amount-of-calls-in-mendix-most-of-the-time-rest-835dde55b10e\n * Rate limit may ~180 req/min\n* https://community.spotify.com/t5/Spotify-for-Developers/Web-API-ratelimit/m-p/5503150/highlight/true#M7930\n * Informally indicated as 20 req/sec? Probably for burstiness","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"spotify client id","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"clientSecret":{"type":"string","description":"spotify client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"spotify redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/callback","examples":["http://localhost:9078/callback"]}},"required":["clientId","clientSecret"],"title":"SpotifySourceData"},"CommonSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonSourceOptions"},"PlexSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","type"],"title":"PlexSourceAIOConfig"},"PlexSourceData":{"type":"object","properties":{"user":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"libraries":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of libraries to scrobble tracks from\n\nIf none are provided tracks from all libraries will be scrobbled","examples":[["Audio","Music"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName"]]}},"title":"PlexSourceData"},"PlexApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/PlexApiOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","options","type"],"title":"PlexApiSourceAIOConfig"},"PlexApiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"token":{"type":"string"},"url":{"type":"string","description":"http(s)://HOST:PORT of the Plex server to connect to"},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"}},"required":["url"],"title":"PlexApiData"},"PlexApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"ignoreInvalidCert":{"type":"boolean","description":"Ignore invalid cert errors when connecting to Plex\n\nUseful for Plex servers using \"Required\" Secure Connections with self-signed certificates\n\nDo not enable unless you know you need this.","default":false}},"title":"PlexApiOptions"},"TautulliSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["tautulli"]}},"required":["data","type"],"title":"TautulliSourceAIOConfig"},"DeezerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerSourceAIOConfig"},"DeezerData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"deezer client id","examples":["a89cba1569901a0671d5a9875fed4be1"]},"clientSecret":{"type":"string","description":"deezer client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/deezer/callback","examples":["http://localhost:9078/deezer/callback"]}},"required":["clientId","clientSecret"],"title":"DeezerData"},"DeezerInternalAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerInternalData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/DeezerInternalSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerInternalAIOConfig"},"DeezerInternalData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"arl":{"type":"string","description":"ARL retrieved from Deezer response header"},"userAgent":{"type":"string","description":"User agent","default":"Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0"}},"required":["arl"],"title":"DeezerInternalData"},"DeezerInternalSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"fuzzyDiscoveryIgnore":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["aggressive"]}]}},"title":"DeezerInternalSourceOptions"},"ListenbrainzEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenbrainzEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlz"]}},"required":["type"],"title":"ListenbrainzEndpointSourceAIOConfig"},"ListenbrainzEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Listenbrainz endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/listenbrainz/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/listenbrainz/originb\n\nIf no slug is found from an extension's incoming webhook event the first Listenbrainz source without a slug will be used"},"token":{"type":["string","null"],"description":"If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source\n\nSee: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#add-the-user-token-to-your-requests"}},"title":"ListenbrainzEndpointData"},"LastFMEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFMEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlfm"]}},"required":["type"],"title":"LastFMEndpointSourceAIOConfig"},"LastFMEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Last.fm endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/lastfm/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/lastfm/originb\n\nIf no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used"}},"title":"LastFMEndpointData"},"SubsonicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SubsonicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["subsonic"]}},"required":["data","type"],"title":"SubsonicSourceAIOConfig"},"SubsonicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the subsonic media server to query","examples":["http://airsonic.local"]},"user":{"type":"string","description":"Username to login to the server with","examples":[["MyUser"]]},"password":{"type":"string","description":"Password for the user to login to the server with","examples":["MyPassword"]},"ignoreTlsErrors":{"type":"boolean","description":"If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection\n\nWARNING: This should be used with caution as your traffic may not be encrypted.","default":false},"legacyAuthentication":{"type":"boolean","description":"Older Subsonic versions, and some badly implemented servers (Nextcloud), use legacy authentication which sends your password in CLEAR TEXT. This is less secure than the newer, recommended hashing authentication method but in some cases it is needed. See \"Authentication\" section here => https://www.subsonic.org/pages/api.jsp\n\nIf this option is not specified it will be turned on if the subsonic server responds with error code 41 \"Token authentication not supported for LDAP users.\" -- See Error Handling section => https://www.subsonic.org/pages/api.jsp","default":false},"usersAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf undefined or an empty string/list MS will scrobble activity from all users"}},"required":["url","user","password"],"title":"SubsonicData"},"JellySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","type"],"title":"JellySourceAIOConfig"},"JellyData":{"type":"object","properties":{"users":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName1"]]}},"title":"JellyData"},"JellyApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/JellyApiOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","options","type"],"title":"JellyApiSourceAIOConfig"},"JellyApiData":{"type":"object","properties":{"url":{"type":"string","description":"HOST:PORT of the Jellyfin server to connect to"},"user":{"type":"string","description":"The username of the user to authenticate for or track scrobbles for"},"password":{"type":"string","description":"Password of the username to authenticate for\n\nRequired if `apiKey` is not provided."},"apiKey":{"type":"string","description":"API Key to authenticate with.\n\nRequired if `password` is not provided."},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"},"additionalAllowedLibraryTypes":{"type":"array","items":{},"description":"Allow MS to scrobble audio media in libraries classified other than 'music'\n\n`librariesAllow` will achieve the same result as this but this is more convenient if you do not want to explicitly list every library name or are only using `librariesBlock`"},"allowUnknown":{"type":"boolean","description":"Force media with a type of \"Unknown\" to be counted as Audio","default":false},"frontendUrlOverride":{"type":"string","description":"HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from)\nIf unspecified it will use the normal server HOST and PORT from the `url`\nNecessary if you are using a reverse proxy or other network configuration that prevents the frontend from accessing the server directly\n\nENV: JELLYFIN_FRONTEND_URL_OVERRIDE"}},"required":["url","user"],"title":"JellyApiData"},"JellyApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"JellyApiOptions"},"LastFmSouceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFmSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["lastfm"]}},"required":["data","type"],"title":"LastFmSouceAIOConfig"},"LastFmSourceData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]},"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"}},"required":["apiKey","secret"],"title":"LastFmSourceData"},"YTMusicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/YTMusicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"type":"object","properties":{"logAuth":{"type":"boolean","description":"When true MS will log to DEBUG all of the credentials data it receives from YTM"},"logDiff":{"type":"boolean","description":"Always log history diff\n\nBy default MS will log to `WARN` if history diff is inconsistent but does not log if diff is expected (on new tracks found)\nSet this to `true` to ALWAYS log diff on new tracks. Expected diffs will log to `DEBUG` and inconsistent diffs will continue to log to `WARN`","default":false},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}}},"type":{"type":"string","enum":["ytmusic"]}},"required":["type"],"title":"YTMusicSourceAIOConfig"},"YTMusicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"cookie":{"type":"string","description":"The cookie retrieved from the Request Headers of music.youtube.com after logging in.\n\nSee https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers for how to retrieve this value.","examples":["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]},"clientId":{"type":"string","description":"Google Cloud Console project OAuth Client ID\n\nGenerated from a custom OAuth Client, see docs"},"clientSecret":{"type":"string","description":"Google Cloud Console project OAuth Client Secret\n\nGenerated from a custom OAuth Client, see docs"},"redirectUri":{"type":"string","description":"Google Cloud Console project OAuth Client Authorized redirect URI\n\nGenerated from a custom OAuth Client, see docs. multi-scrobbler will generate a default based on BASE_URL.\nOnly specify this if the default does not work for you."},"innertubeOptions":{"$ref":"#/definitions/InnertubeOptions","description":"Additional options for authorization and tailoring YTM client"}},"title":"YTMusicData"},"InnertubeOptions":{"type":"object","properties":{"po_token":{"type":"string","description":"Proof of Origin token\n\nMay be required if YTM starts returning 403"},"visitor_data":{"type":"string","description":"Visitor ID value found in VISITOR_INFO1_LIVE or visitorData cookie\n\nMay be required if YTM starts returning 403"},"account_index":{"type":"number","description":"If account login results in being able to choose multiple account, use a zero-based index to choose which one to monitor","examples":[0,1]},"location":{"type":"string"},"lang":{"type":"string"},"generate_session_locally":{"type":"boolean"},"device_category":{"type":"string"},"client_type":{"type":"string"},"timezone":{"type":"string"}},"title":"InnertubeOptions"},"MPRISSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPRISData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mpris"]}},"required":["data","type"],"title":"MPRISSourceAIOConfig"},"MPRISData":{"type":"object","properties":{"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any players that START WITH these values, case-insensitive","examples":[["spotify","vlc"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","vlc"]]}},"title":"MPRISData"},"MopidySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MopidyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mopidy"]}},"required":["data","type"],"title":"MopidySourceAIOConfig"},"MopidyData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Mopidy HTTP server to connect to\n\nYou MUST have Mopidy-HTTP extension enabled: https://mopidy.com/ext/http\n\nmulti-scrobbler connects to the WebSocket endpoint that ultimately looks like this => `ws://localhost:6680/mopidy/ws/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `6680`\n* Path => `/mopidy/ws/`","examples":["ws://localhost:6680/mopidy/ws/"],"default":"ws://localhost:6680/mopidy/ws/"},"uriBlacklist":{"type":"array","items":{"type":"string"},"description":"Do not scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Don't scrobble tracks from soundcloud by adding 'soundcloud' to this list.\n\nList is ignored if uriWhitelist is used."},"uriWhitelist":{"type":"array","items":{"type":"string"},"description":"Only scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Only scrobble tracks from soundcloud by adding 'soundcloud' to this list."},"albumBlacklist":{"type":"array","items":{"type":"string"},"description":"Remove album data that matches any case-insensitive string from this list when scrobbling,\n\nFor certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use \"Soundcloud\" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake name to this list. Multi-scrobbler will still scrobble the track, just without the bad data.","examples":[["Soundcloud","Mixcloud"]],"default":["Soundcloud"]}},"title":"MopidyData"},"ListenBrainzSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenBrainzSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `listenbrainz.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["listenbrainz"]}},"required":["data","type"],"title":"ListenBrainzSourceAIOConfig"},"ListenBrainzSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzSourceData"},"JRiverSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JRiverData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jriver"]}},"required":["data","type"],"title":"JRiverSourceAIOConfig"},"JRiverData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the JRiver HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:52199/MCWS/v1/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `52199`\n* Path => `/MCWS/v1/`","examples":["http://localhost:52199/MCWS/v1/"],"default":"http://localhost:52199/MCWS/v1/"},"username":{"type":"string","description":"If you have enabled authentication, the username you set"},"password":{"type":"string","description":"If you have enabled authentication, the password you set"}},"required":["url"],"title":"JRiverData"},"KodiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KodiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["kodi"]}},"required":["data","type"],"title":"KodiSourceAIOConfig"},"KodiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Kodi HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:8080/jsonrpc`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `8080`\n* Path => `/jsonrpc`","examples":["http://localhost:8080/jsonrpc"],"default":"http://localhost:8080/jsonrpc"},"username":{"type":"string","description":"The username set for Remote Control via Web Sever"},"password":{"type":"string","description":"The password set for Remote Control via Web Sever"}},"required":["url","username","password"],"title":"KodiData"},"WebScrobblerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/WebScrobblerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["webscrobbler"]}},"required":["type"],"title":"WebScrobblerSourceAIOConfig"},"WebScrobblerData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIn WebScrobbler's Webhook you must set an 'API URL'. All MS WebScrobbler sources must start like:\n\nhttp://localhost:9078/api/webscrobbler\n\nIf you are using multiple WebScrobbler sources (scrobbles for many users) you must use a slug to match Sources with each users extension.\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/webscrobbler/usera\n* slug: 'userb' => API URL: http://localhost:9078/api/webscrobbler/userb\n\nIf no slug is found from an extension's incoming webhook event the first WebScrobbler source without a slug will be used"},"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Block scrobbling from specific WebScrobbler Connectors","examples":[["youtube"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only allow scrobbling from specific WebScrobbler Connectors","examples":[["mixcloud","soundcloud","bandcamp"]]}},"title":"WebScrobblerData"},"ChromecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ChromecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["chromecast"]}},"required":["data","type"],"title":"ChromecastSourceAIOConfig"},"ChromecastData":{"type":"object","properties":{"blacklistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"whitelistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any cast device that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"blacklistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any application that START WITH these values, case-insensitive","examples":[["spotify","pandora"]]},"whitelistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any application that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","pandora"]]},"useAvahi":{"type":"boolean","description":"Try to use Avahi and avahi-browse to resolve mDNS devices instead of native mDNS querying\n\nUseful for docker (alpine) container where mDNS resolution is not yet supported. Avahi socket must be exposed to the container and avahi-tools must be installed.","default":false},"useAutoDiscovery":{"type":"boolean","description":"Use mDNS to discovery Google Cast devices on your next automatically?\n\nIf not explicitly set then it is TRUE if `devices` is not set"},"devices":{"type":"array","items":{"$ref":"#/definitions/ChromecastDeviceInfo"},"description":"A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set"},"allowUnknownMedia":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Chromecast Apps report a \"media type\" in the status info returned for whatever is currently playing\n\n* If set to TRUE then Music AND Generic/Unknown media will be tracked for ALL APPS\n* If set to FALSE then only media explicitly typed as Music will be tracked for ALL APPS\n* If set to a list then only Apps whose name contain one of these values, case-insensitive, will have Music AND Generic/Unknown tracked\n\nSee https://developers.google.com/cast/docs/media/messages#MediaInformation \"metadata\" property","default":false},"forceMediaRecognitionOn":{"type":"array","items":{"type":"string"},"description":"Media provided by any App whose name is listed here will ALWAYS be tracked, regardless of the \"media type\" reported\n\nApps will be recognized if they CONTAIN any of these values, case-insensitive"}},"title":"ChromecastData"},"ChromecastDeviceInfo":{"type":"object","properties":{"name":{"type":"string","description":"A friendly name to identify this device","examples":["MySmartTV"]},"address":{"type":"string","description":"The IP address of the device","examples":["192.168.0.115"]}},"required":["name","address"],"title":"ChromecastDeviceInfo"},"MalojaSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MalojaSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `maloja.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["maloja"]}},"required":["data","type"],"title":"MalojaSourceAIOConfig"},"MalojaSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaSourceData"},"MusikcubeSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusikcubeData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musikcube"]}},"required":["data","type"],"title":"MusikcubeSourceAIOConfig"},"MusikcubeData":{"type":"object","properties":{"url":{"type":"string","description":"URL of the Musikcube Websocket (Metadata) server to connect to\n\nYou MUST have enabled 'metadata' server and set a password: https://github.com/clangen/musikcube/wiki/remote-api-documentation\n * musikcube -> settings -> server setup\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `7905`","examples":["ws://localhost:7905"],"default":"ws://localhost:7905"},"password":{"type":"string","description":"Password set in Musikcube https://github.com/clangen/musikcube/wiki/remote-api-documentation\n\n* musikcube -> settings -> server setup -> password"},"device_id":{"type":"string"}},"required":["password"],"title":"MusikcubeData"},"MusicCastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusicCastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musiccast"]}},"required":["data","type"],"title":"MusicCastSourceAIOConfig"},"MusicCastData":{"type":"object","properties":{"url":{"type":"string","description":"The host or URL of the YamahaExtendedControl endpoint to use","examples":[["192.168.0.101","http://192.168.0.101/YamahaExtendedControl"]]}},"required":["url"],"title":"MusicCastData"},"MPDSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPDData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/MPDSourceOptions"},"type":{"type":"string","enum":["mpd"]}},"required":["data","options","type"],"title":"MPDSourceAIOConfig"},"MPDData":{"type":"object","properties":{"url":{"type":"string","description":"URL:PORT of the MPD server to connect to\n\nTo use this you must have TCP connections enabled for your MPD server https://mpd.readthedocs.io/en/stable/user.html#client-connections","examples":["localhost:6600"],"default":"localhost:6600"},"path":{"type":"string","description":"If using socket specify the path instead of url.\n\ntrailing `~` is replaced by your home directory"},"password":{"type":"string","description":"Password for the server, if set https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords"}},"title":"MPDData"},"MPDSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"MPDSourceOptions"},"VLCSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/VLCData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/VLCSourceOptions"},"type":{"type":"string","enum":["vlc"]}},"required":["data","type"],"title":"VLCSourceAIOConfig"},"VLCData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL:PORT of the VLC server to connect to\n\nTo use this you must have the Web (http) interface module enabled and a password set https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc","examples":["localhost:8080"],"default":"localhost:8080"},"password":{"type":"string","description":"Password for the server"}},"required":["password"],"title":"VLCData"},"VLCSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"filenamePatterns":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A list of regular expressions to use to extract metadata (title, album, artist) from a filename\n\nUsed when VLC reports only the filename for the current audio track"},"logFilenamePatterns":{"type":"boolean","description":"Log to DEBUG when a filename-only track is matched or not matched by filenamePatterns","default":false},"dumpVlcMetadata":{"type":"boolean","description":"Dump all the metadata VLC reports for an audio track to DEBUG.\n\nUse this if reporting an issue with VLC not correctly capturing metadata for a track.","default":false}},"title":"VLCSourceOptions"},"IcecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/IcecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/IcecastSourceOptions"},"type":{"type":"string","enum":["icecast"]}},"required":["data","type"],"title":"IcecastSourceAIOConfig"},"IcecastData":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/definitions/IcecastSource"}},"icestatsEndpoint":{"type":"string"},"statsEndpoint":{"type":"string"},"nextsongsEndpoint":{"type":"string"},"sevenhtmlEndpoint":{"type":"string"},"icyMetaInt":{"type":"number"},"url":{"type":"string","description":"The Icecast stream URL"}},"required":["url"],"title":"IcecastData"},"IcecastSource":{"type":"string","enum":["icy","ogg","icestats","stats","sevenhtml","nextsongs"],"title":"IcecastSource"},"IcecastSourceOptions":{"type":"object","properties":{"systemScrobble":{"type":"boolean","description":"For Sources that support manual listening, should MS default to scrobbling when no user interaction has occurred?\n\nIf not specified MS will use a Source's specific behavior, see Source's documentation."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"IcecastSourceOptions"},"AzuracastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/AzuracastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["azuracast"]}},"required":["data","type"],"title":"AzuracastSourceAIOConfig"},"AzuracastData":{"type":"object","properties":{"url":{"type":"string","description":"Base URL of the Azuracast instance\n\nThis does NOT include the station. If a station is included it will be ignored. Use `station` field to specify station, if necessary","examples":["https://radio.mydomain.tld","http://localhost:80"]},"station":{"type":"string","description":"The specific station to monitor\n\nScrobbling will only occur if any of the monitor conditions are met AND the station is ONLINE.\n\nTo monitor multiple stations create a Source for each station.","examples":["my-station-1"]},"monitorWhenListeners":{"type":["boolean","number"],"description":"Only activate scrobble monitoring if station\n\n* `true` => has any current listeners\n* `number` => has EQUAL TO or MORE THAN X number of listeners"},"monitorWhenLive":{"type":"boolean","description":"Only activate scrobble monitoring if station has a live DJ/Streamer","default":true},"apiKey":{"type":"string","description":"API Key used to access data about private streams\n\nhttps://www.azuracast.com/docs/developers/apis/#api-authentication"}},"required":["url","station"],"title":"AzuracastData"},"KoitoSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KoitoSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `koito.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["koito"]}},"required":["data","type"],"title":"KoitoSourceAIOConfig"},"KoitoSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoSourceData"},"TealSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/TealSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/TealSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"Should always be `souce` when using Tealfm as a Source","default":"source","examples":["source"]},"type":{"type":"string","enum":["tealfm"]}},"required":["data","type"],"title":"TealSourceAIOConfig"},"TealSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealSourceData"},"TealSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealSourceOptions"},"RockskySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/RockskySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/RockskySourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `rocksky.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["rocksky"]}},"required":["data","type"],"title":"RockskySourceAIOConfig"},"RockskySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockskySourceData"},"RockskySourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockskySourceOptions"},"ClientAIOConfig":{"anyOf":[{"$ref":"#/definitions/MalojaClientAIOConfig"},{"$ref":"#/definitions/LastfmClientAIOConfig"},{"$ref":"#/definitions/ListenBrainzClientAIOConfig"},{"$ref":"#/definitions/KoitoClientAIOConfig"},{"$ref":"#/definitions/TealClientAIOConfig"},{"$ref":"#/definitions/RockSkyClientAIOConfig"}],"title":"ClientAIOConfig"},"MalojaClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/MalojaClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Maloja as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["maloja"]}},"required":["data","name","type"],"title":"MalojaClientAIOConfig"},"MalojaClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaClientData"},"CommonClientOptions":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonClientOptions"},"LastfmClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/LastfmData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/LastfmClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using LastFM as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["lastfm"]}},"required":["data","name","type"],"title":"LastfmClientAIOConfig"},"LastfmData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]}},"required":["apiKey","secret"],"title":"LastfmData"},"LastfmClientOptions":{"type":"object","properties":{"nowPlaying":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Configure if this Client should report Now Playing from Sources that can scrobble to it\n\n* `true` (default) => Report Now Playing from any eligible Source. \n * If multiple Sources are Playing then reported Play is based on alphabetical order of Source names\n* `false` => Do not report Now Playing\n* `string` list => list of Source `names` that should be allowed to report Now Playing. Order of list determine priority of Play to Report.","default":true},"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"LastfmClientOptions"},"ListenBrainzClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/ListenBrainzClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Listenbrainz as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["listenbrainz"]}},"required":["data","name","type"],"title":"ListenBrainzClientAIOConfig"},"ListenBrainzClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzClientData"},"KoitoClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/KoitoClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Koito as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["koito"]}},"required":["data","name","type"],"title":"KoitoClientAIOConfig"},"KoitoClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoClientData"},"TealClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/TealClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/TealClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Tealfm as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["tealfm"]}},"required":["data","name","type"],"title":"TealClientAIOConfig"},"TealClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealClientData"},"TealClientOptions":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealClientOptions"},"RockSkyClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/RockSkyClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/RockSkyClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using RockSky as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["rocksky"]}},"required":["data","name","type"],"title":"RockSkyClientAIOConfig"},"RockSkyClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockSkyClientData"},"RockSkyClientOptions":{"type":"object","properties":{"nowPlaying":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Configure if this Client should report Now Playing from Sources that can scrobble to it\n\n* `true` (default) => Report Now Playing from any eligible Source. \n * If multiple Sources are Playing then reported Play is based on alphabetical order of Source names\n* `false` => Do not report Now Playing\n* `string` list => list of Source `names` that should be allowed to report Now Playing. Order of list determine priority of Play to Report.","default":true},"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockSkyClientOptions"},"WebhookConfig":{"anyOf":[{"$ref":"#/definitions/GotifyConfig"},{"$ref":"#/definitions/NtfyConfig"},{"$ref":"#/definitions/AppriseConfig"}],"title":"WebhookConfig"},"GotifyConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"url":{"type":"string","description":"The URL of the Gotify server. Same URL that would be used to reach the Gotify UI","examples":["http://192.168.0.100:8078"]},"token":{"type":"string","description":"The token created for this Application in Gotify","examples":["AQZI58fA.rfSZbm"]},"priorities":{"$ref":"#/definitions/PrioritiesConfig","description":"Priority of messages\n\n* Info -> 5\n* Warn -> 7\n* Error -> 10"}},"required":["token","type","url"],"title":"GotifyConfig"},"PrioritiesConfig":{"type":"object","properties":{"info":{"type":"number","examples":[5]},"warn":{"type":"number","examples":[7]},"error":{"type":"number","examples":[10]}},"required":["info","warn","error"],"title":"PrioritiesConfig"},"NtfyConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"url":{"type":"string","description":"The URL of the Ntfy server","examples":["http://192.168.0.100:8078"]},"topic":{"type":"string","description":"The topic mutli-scrobbler should POST to"},"username":{"type":"string","description":"Required if topic is protected"},"password":{"type":"string","description":"Required if topic is protected"},"token":{"type":"string","description":"Use instead of username/password, required if topic is protected"},"priorities":{"$ref":"#/definitions/PrioritiesConfig","description":"Priority of messages\n\n* Info -> 3\n* Warn -> 4\n* Error -> 5"}},"required":["topic","type","url"],"title":"NtfyConfig"},"AppriseConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"host":{"type":"string","description":"The URL of the apprise-api server","examples":["http://192.168.0.100:8078"]},"urls":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"If using [Stateless Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#stateless-solution) the Apprise config URL(s) to send"},"keys":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"If using [Persistent Store Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#persistent-storage-solution) the Configuration ID(s) to send to\n\nNote: If multiple keys are defined then MS will attempt to POST to each one individually"},"tags":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Optional [tag(s)](https://github.com/caronc/apprise-api?tab=readme-ov-file#tagging) to send in the notification payload"}},"required":["host","type"],"title":"AppriseConfig"},"LogOptions":{"type":"object","properties":{"level":{"$ref":"#/definitions/LogLevel","description":"Specify the minimum log level for all log outputs without their own level specified.\n\nDefaults to env `LOG_LEVEL` or `info` if not specified.","default":"info"},"file":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false},{"$ref":"#/definitions/FileLogOptions"}],"description":"Specify the minimum log level to output to rotating files or file output options. If `false` no log files will be created."},"console":{"$ref":"#/definitions/LogLevel","description":"Specify the minimum log level streamed to the console (or docker container)"}},"description":"Configure log levels and file options for an AppLogger.\n\n```ts\nconst infoLogger = loggerApp({\n level: 'info' // console and file will log any levels `info` and above\n});\n\nconst logger = loggerApp({\n console: 'debug', // console will log `debug` and higher\n file: 'warn' // file will log `warn` and higher\n});\n\nconst fileLogger = loggerRollingApp({\n console: 'debug', // console will log `debug` and higher\n file: {\n level: 'warn', // file will log `warn` and higher\n path: '/my/cool/path/output.log', // optionally, output to log file at this path\n frequency: 'hourly', // optionally, rotate hourly\n }\n});\n```","title":"LogOptions"},"CacheConfigOptions":{"type":"object","properties":{"metadata":{"$ref":"#/definitions/CacheMetadataConfig"},"scrobble":{"$ref":"#/definitions/CacheScrobbleConfig"},"auth":{"$ref":"#/definitions/CacheAuthConfig"}},"title":"CacheConfigOptions"},"CacheMetadataConfig":{"$ref":"#/definitions/CacheConfig%3CCacheMetadataProvider%3E","title":"CacheMetadataConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheMetadataProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheMetadataProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheMetadataProvider"},"CacheProvider":{"type":["string","boolean"],"enum":["memory","valkey","file",false],"title":"CacheProvider"},"CacheScrobbleConfig":{"$ref":"#/definitions/CacheConfig%3CCacheScrobbleProvider%3E","title":"CacheScrobbleConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheScrobbleProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheScrobbleProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheScrobbleProvider"},"CacheAuthConfig":{"$ref":"#/definitions/CacheConfig%3CCacheAuthProvider%3E","title":"CacheAuthConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheAuthProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheAuthProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheAuthProvider"}}} diff --git a/docsite/static/source.json b/docsite/static/source.json index 26705a06..53c4d3a8 100644 --- a/docsite/static/source.json +++ b/docsite/static/source.json @@ -1 +1 @@ -{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"$ref":"#/definitions/SpotifySourceConfig"},{"$ref":"#/definitions/PlexSourceConfig"},{"$ref":"#/definitions/PlexApiSourceConfig"},{"$ref":"#/definitions/TautulliSourceConfig"},{"$ref":"#/definitions/DeezerSourceConfig"},{"$ref":"#/definitions/DeezerInternalSourceConfig"},{"$ref":"#/definitions/ListenbrainzEndpointSourceConfig"},{"$ref":"#/definitions/LastFMEndpointSourceConfig"},{"$ref":"#/definitions/SubSonicSourceConfig"},{"$ref":"#/definitions/JellySourceConfig"},{"$ref":"#/definitions/JellyApiSourceConfig"},{"$ref":"#/definitions/LastfmSourceConfig"},{"$ref":"#/definitions/YTMusicSourceConfig"},{"$ref":"#/definitions/MPRISSourceConfig"},{"$ref":"#/definitions/MopidySourceConfig"},{"$ref":"#/definitions/ListenBrainzSourceConfig"},{"$ref":"#/definitions/JRiverSourceConfig"},{"$ref":"#/definitions/KodiSourceConfig"},{"$ref":"#/definitions/WebScrobblerSourceConfig"},{"$ref":"#/definitions/ChromecastSourceConfig"},{"$ref":"#/definitions/MalojaSourceConfig"},{"$ref":"#/definitions/MusikcubeSourceConfig"},{"$ref":"#/definitions/MusicCastSourceConfig"},{"$ref":"#/definitions/MPDSourceConfig"},{"$ref":"#/definitions/VLCSourceConfig"},{"$ref":"#/definitions/IcecastSourceConfig"},{"$ref":"#/definitions/AzuracastSourceConfig"},{"$ref":"#/definitions/KoitoSourceConfig"},{"$ref":"#/definitions/TealSourceConfig"},{"$ref":"#/definitions/RockskySourceConfig"}],"definitions":{"SpotifySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SpotifySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"SpotifySourceConfig"},"SpotifySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)\n\nIt is unlikely you should need to change this unless you scrobble many very short tracks often\n\nReading:\n* https://developer.spotify.com/documentation/web-api/guides/rate-limits/\n* https://medium.com/mendix/limiting-your-amount-of-calls-in-mendix-most-of-the-time-rest-835dde55b10e\n * Rate limit may ~180 req/min\n* https://community.spotify.com/t5/Spotify-for-Developers/Web-API-ratelimit/m-p/5503150/highlight/true#M7930\n * Informally indicated as 20 req/sec? Probably for burstiness","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"spotify client id","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"clientSecret":{"type":"string","description":"spotify client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"spotify redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/callback","examples":["http://localhost:9078/callback"]}},"required":["clientId","clientSecret"],"title":"SpotifySourceData"},"CommonSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonSourceOptions"},"LogLevel":{"type":"string","enum":["silent","fatal","error","warn","info","log","verbose","debug"],"description":"Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.","title":"LogLevel"},"FileLogOptions":{"type":"object","properties":{"timestamp":{"type":"string","enum":["unix","iso","auto"],"description":"For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime","default":"auto"},"size":{"type":["number","string"],"description":"The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.","default":"10MB"},"frequency":{"anyOf":[{"type":"string","enum":["daily"]},{"type":"string","enum":["hourly"]},{"type":"number"}],"description":"The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.","default":"daily"},"path":{"anyOf":[{"type":"string"},{"$comment":"() => string"}],"description":"The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD","default":"./logs/app.log"},"level":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false}],"description":"Specify the minimum log level to output to rotating files. If `false` no log files will be created."}},"title":"FileLogOptions"},"ScrobbleThresholds":{"type":"object","properties":{"duration":{"type":["number","null"],"description":"The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.","default":240,"examples":[240]},"percent":{"type":["number","null"],"description":"The percentage (as an integer) of a track that should have been seen played before it should be scrobbled. Only used if the Source provides information about how long the track is.\n\nSet to null to disable.\n\nNOTE: This should be used with care when the Source is a \"polling\" type (has an 'interval' property). If the track is short and the interval is too high MS may ignore the track if percentage is high because it had not \"seen\" the track for long enough from first discovery, even if you have been playing the track for longer.","default":50,"examples":[50]}},"title":"ScrobbleThresholds"},"PlayTransformOptions":{"type":"object","properties":{"log":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["all"]}]},"preCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"compare":{"type":"object","properties":{"candidate":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"existing":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}}},"postCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}},"title":"PlayTransformOptions"},"PlayTransformPartsConfig":{"anyOf":[{"$ref":"#/definitions/PlayTransformPartsArray%3CSearchAndReplaceTerm%3E"},{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"}],"title":"PlayTransformPartsConfig"},"PlayTransformPartsArray":{"type":"array","items":{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"},"title":"PlayTransformPartsArray"},"PlayTransformParts":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"title":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"artists":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"album":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}}},"title":"PlayTransformParts"},"WhenConditionsConfig":{"$ref":"#/definitions/WhenConditions%3Cstring%3E","title":"WhenConditionsConfig"},"WhenConditions":{"type":"array","items":{"$ref":"#/definitions/WhenParts%3Cstring%3E"},"title":"WhenConditions"},"WhenParts":{"$ref":"#/definitions/PlayTransformPartsAtomic%3Cstring%3E","title":"WhenParts"},"PlayTransformPartsAtomic":{"type":"object","properties":{"title":{"type":"string"},"artists":{"type":"string"},"album":{"type":"string"}},"title":"PlayTransformPartsAtomic"},"SearchAndReplaceTerm":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/ConditionalSearchAndReplaceTerm"}],"title":"SearchAndReplaceTerm"},"ConditionalSearchAndReplaceTerm":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"search":{},"replace":{}},"required":["search","replace"],"title":"ConditionalSearchAndReplaceTerm"},"PlexSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"PlexSourceConfig"},"PlexSourceData":{"type":"object","properties":{"user":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"libraries":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of libraries to scrobble tracks from\n\nIf none are provided tracks from all libraries will be scrobbled","examples":[["Audio","Music"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName"]]}},"title":"PlexSourceData"},"PlexApiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/PlexApiOptions"}},"required":["data","options"],"title":"PlexApiSourceConfig"},"PlexApiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"token":{"type":"string"},"url":{"type":"string","description":"http(s)://HOST:PORT of the Plex server to connect to"},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"}},"required":["url"],"title":"PlexApiData"},"PlexApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"ignoreInvalidCert":{"type":"boolean","description":"Ignore invalid cert errors when connecting to Plex\n\nUseful for Plex servers using \"Required\" Secure Connections with self-signed certificates\n\nDo not enable unless you know you need this.","default":false}},"title":"PlexApiOptions"},"TautulliSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"TautulliSourceConfig"},"DeezerSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"DeezerSourceConfig"},"DeezerData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"deezer client id","examples":["a89cba1569901a0671d5a9875fed4be1"]},"clientSecret":{"type":"string","description":"deezer client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/deezer/callback","examples":["http://localhost:9078/deezer/callback"]}},"required":["clientId","clientSecret"],"title":"DeezerData"},"DeezerInternalSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerInternalData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/DeezerInternalSourceOptions"}},"required":["data"],"title":"DeezerInternalSourceConfig"},"DeezerInternalData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"arl":{"type":"string","description":"ARL retrieved from Deezer response header"},"userAgent":{"type":"string","description":"User agent","default":"Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0"}},"required":["arl"],"title":"DeezerInternalData"},"DeezerInternalSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"fuzzyDiscoveryIgnore":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["aggressive"]}]}},"title":"DeezerInternalSourceOptions"},"ListenbrainzEndpointSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenbrainzEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"ListenbrainzEndpointSourceConfig"},"ListenbrainzEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Listenbrainz endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/listenbrainz/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/listenbrainz/originb\n\nIf no slug is found from an extension's incoming webhook event the first Listenbrainz source without a slug will be used"},"token":{"type":["string","null"],"description":"If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source\n\nSee: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#add-the-user-token-to-your-requests"}},"title":"ListenbrainzEndpointData"},"LastFMEndpointSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFMEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"LastFMEndpointSourceConfig"},"LastFMEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Last.fm endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/lastfm/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/lastfm/originb\n\nIf no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used"}},"title":"LastFMEndpointData"},"SubSonicSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SubsonicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"SubSonicSourceConfig"},"SubsonicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the subsonic media server to query","examples":["http://airsonic.local"]},"user":{"type":"string","description":"Username to login to the server with","examples":[["MyUser"]]},"password":{"type":"string","description":"Password for the user to login to the server with","examples":["MyPassword"]},"ignoreTlsErrors":{"type":"boolean","description":"If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection\n\nWARNING: This should be used with caution as your traffic may not be encrypted.","default":false},"legacyAuthentication":{"type":"boolean","description":"Older Subsonic versions, and some badly implemented servers (Nextcloud), use legacy authentication which sends your password in CLEAR TEXT. This is less secure than the newer, recommended hashing authentication method but in some cases it is needed. See \"Authentication\" section here => https://www.subsonic.org/pages/api.jsp\n\nIf this option is not specified it will be turned on if the subsonic server responds with error code 41 \"Token authentication not supported for LDAP users.\" -- See Error Handling section => https://www.subsonic.org/pages/api.jsp","default":false},"usersAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf undefined or an empty string/list MS will scrobble activity from all users"}},"required":["url","user","password"],"title":"SubsonicData"},"JellySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"JellySourceConfig"},"JellyData":{"type":"object","properties":{"users":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName1"]]}},"title":"JellyData"},"JellyApiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/JellyApiOptions"}},"required":["data","options"],"title":"JellyApiSourceConfig"},"JellyApiData":{"type":"object","properties":{"url":{"type":"string","description":"HOST:PORT of the Jellyfin server to connect to"},"user":{"type":"string","description":"The username of the user to authenticate for or track scrobbles for"},"password":{"type":"string","description":"Password of the username to authenticate for\n\nRequired if `apiKey` is not provided."},"apiKey":{"type":"string","description":"API Key to authenticate with.\n\nRequired if `password` is not provided."},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"},"additionalAllowedLibraryTypes":{"type":"array","items":{},"description":"Allow MS to scrobble audio media in libraries classified other than 'music'\n\n`librariesAllow` will achieve the same result as this but this is more convenient if you do not want to explicitly list every library name or are only using `librariesBlock`"},"allowUnknown":{"type":"boolean","description":"Force media with a type of \"Unknown\" to be counted as Audio","default":false},"frontendUrlOverride":{"type":"string","description":"HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from)\nIf unspecified it will use the normal server HOST and PORT from the `url`\nNecessary if you are using a reverse proxy or other network configuration that prevents the frontend from accessing the server directly\n\nENV: JELLYFIN_FRONTEND_URL_OVERRIDE"}},"required":["url","user"],"title":"JellyApiData"},"JellyApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"JellyApiOptions"},"LastfmSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFmSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"LastfmSourceConfig"},"LastFmSourceData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]},"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"}},"required":["apiKey","secret"],"title":"LastFmSourceData"},"YTMusicSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/YTMusicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"type":"object","properties":{"logAuth":{"type":"boolean","description":"When true MS will log to DEBUG all of the credentials data it receives from YTM"},"logDiff":{"type":"boolean","description":"Always log history diff\n\nBy default MS will log to `WARN` if history diff is inconsistent but does not log if diff is expected (on new tracks found)\nSet this to `true` to ALWAYS log diff on new tracks. Expected diffs will log to `DEBUG` and inconsistent diffs will continue to log to `WARN`","default":false},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}}}},"title":"YTMusicSourceConfig"},"YTMusicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"cookie":{"type":"string","description":"The cookie retrieved from the Request Headers of music.youtube.com after logging in.\n\nSee https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers for how to retrieve this value.","examples":["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]},"clientId":{"type":"string","description":"Google Cloud Console project OAuth Client ID\n\nGenerated from a custom OAuth Client, see docs"},"clientSecret":{"type":"string","description":"Google Cloud Console project OAuth Client Secret\n\nGenerated from a custom OAuth Client, see docs"},"redirectUri":{"type":"string","description":"Google Cloud Console project OAuth Client Authorized redirect URI\n\nGenerated from a custom OAuth Client, see docs. multi-scrobbler will generate a default based on BASE_URL.\nOnly specify this if the default does not work for you."},"innertubeOptions":{"$ref":"#/definitions/InnertubeOptions","description":"Additional options for authorization and tailoring YTM client"}},"title":"YTMusicData"},"InnertubeOptions":{"type":"object","properties":{"po_token":{"type":"string","description":"Proof of Origin token\n\nMay be required if YTM starts returning 403"},"visitor_data":{"type":"string","description":"Visitor ID value found in VISITOR_INFO1_LIVE or visitorData cookie\n\nMay be required if YTM starts returning 403"},"account_index":{"type":"number","description":"If account login results in being able to choose multiple account, use a zero-based index to choose which one to monitor","examples":[0,1]},"location":{"type":"string"},"lang":{"type":"string"},"generate_session_locally":{"type":"boolean"},"device_category":{"type":"string"},"client_type":{"type":"string"},"timezone":{"type":"string"}},"title":"InnertubeOptions"},"MPRISSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPRISData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MPRISSourceConfig"},"MPRISData":{"type":"object","properties":{"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any players that START WITH these values, case-insensitive","examples":[["spotify","vlc"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","vlc"]]}},"title":"MPRISData"},"MopidySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MopidyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MopidySourceConfig"},"MopidyData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Mopidy HTTP server to connect to\n\nYou MUST have Mopidy-HTTP extension enabled: https://mopidy.com/ext/http\n\nmulti-scrobbler connects to the WebSocket endpoint that ultimately looks like this => `ws://localhost:6680/mopidy/ws/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `6680`\n* Path => `/mopidy/ws/`","examples":["ws://localhost:6680/mopidy/ws/"],"default":"ws://localhost:6680/mopidy/ws/"},"uriBlacklist":{"type":"array","items":{"type":"string"},"description":"Do not scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Don't scrobble tracks from soundcloud by adding 'soundcloud' to this list.\n\nList is ignored if uriWhitelist is used."},"uriWhitelist":{"type":"array","items":{"type":"string"},"description":"Only scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Only scrobble tracks from soundcloud by adding 'soundcloud' to this list."},"albumBlacklist":{"type":"array","items":{"type":"string"},"description":"Remove album data that matches any case-insensitive string from this list when scrobbling,\n\nFor certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use \"Soundcloud\" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake name to this list. Multi-scrobbler will still scrobble the track, just without the bad data.","examples":[["Soundcloud","Mixcloud"]],"default":["Soundcloud"]}},"title":"MopidyData"},"ListenBrainzSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenBrainzSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `listenbrainz.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"ListenBrainzSourceConfig"},"ListenBrainzSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzSourceData"},"JRiverSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JRiverData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"JRiverSourceConfig"},"JRiverData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the JRiver HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:52199/MCWS/v1/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `52199`\n* Path => `/MCWS/v1/`","examples":["http://localhost:52199/MCWS/v1/"],"default":"http://localhost:52199/MCWS/v1/"},"username":{"type":"string","description":"If you have enabled authentication, the username you set"},"password":{"type":"string","description":"If you have enabled authentication, the password you set"}},"required":["url"],"title":"JRiverData"},"KodiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KodiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"KodiSourceConfig"},"KodiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Kodi HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:8080/jsonrpc`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `8080`\n* Path => `/jsonrpc`","examples":["http://localhost:8080/jsonrpc"],"default":"http://localhost:8080/jsonrpc"},"username":{"type":"string","description":"The username set for Remote Control via Web Sever"},"password":{"type":"string","description":"The password set for Remote Control via Web Sever"}},"required":["url","username","password"],"title":"KodiData"},"WebScrobblerSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/WebScrobblerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"WebScrobblerSourceConfig"},"WebScrobblerData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIn WebScrobbler's Webhook you must set an 'API URL'. All MS WebScrobbler sources must start like:\n\nhttp://localhost:9078/api/webscrobbler\n\nIf you are using multiple WebScrobbler sources (scrobbles for many users) you must use a slug to match Sources with each users extension.\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/webscrobbler/usera\n* slug: 'userb' => API URL: http://localhost:9078/api/webscrobbler/userb\n\nIf no slug is found from an extension's incoming webhook event the first WebScrobbler source without a slug will be used"},"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Block scrobbling from specific WebScrobbler Connectors","examples":[["youtube"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only allow scrobbling from specific WebScrobbler Connectors","examples":[["mixcloud","soundcloud","bandcamp"]]}},"title":"WebScrobblerData"},"ChromecastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ChromecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"ChromecastSourceConfig"},"ChromecastData":{"type":"object","properties":{"blacklistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"whitelistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any cast device that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"blacklistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any application that START WITH these values, case-insensitive","examples":[["spotify","pandora"]]},"whitelistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any application that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","pandora"]]},"useAvahi":{"type":"boolean","description":"Try to use Avahi and avahi-browse to resolve mDNS devices instead of native mDNS querying\n\nUseful for docker (alpine) container where mDNS resolution is not yet supported. Avahi socket must be exposed to the container and avahi-tools must be installed.","default":false},"useAutoDiscovery":{"type":"boolean","description":"Use mDNS to discovery Google Cast devices on your next automatically?\n\nIf not explicitly set then it is TRUE if `devices` is not set"},"devices":{"type":"array","items":{"$ref":"#/definitions/ChromecastDeviceInfo"},"description":"A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set"},"allowUnknownMedia":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Chromecast Apps report a \"media type\" in the status info returned for whatever is currently playing\n\n* If set to TRUE then Music AND Generic/Unknown media will be tracked for ALL APPS\n* If set to FALSE then only media explicitly typed as Music will be tracked for ALL APPS\n* If set to a list then only Apps whose name contain one of these values, case-insensitive, will have Music AND Generic/Unknown tracked\n\nSee https://developers.google.com/cast/docs/media/messages#MediaInformation \"metadata\" property","default":false},"forceMediaRecognitionOn":{"type":"array","items":{"type":"string"},"description":"Media provided by any App whose name is listed here will ALWAYS be tracked, regardless of the \"media type\" reported\n\nApps will be recognized if they CONTAIN any of these values, case-insensitive"}},"title":"ChromecastData"},"ChromecastDeviceInfo":{"type":"object","properties":{"name":{"type":"string","description":"A friendly name to identify this device","examples":["MySmartTV"]},"address":{"type":"string","description":"The IP address of the device","examples":["192.168.0.115"]}},"required":["name","address"],"title":"ChromecastDeviceInfo"},"MalojaSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MalojaSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `maloja.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"MalojaSourceConfig"},"MalojaSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaSourceData"},"MusikcubeSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusikcubeData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MusikcubeSourceConfig"},"MusikcubeData":{"type":"object","properties":{"url":{"type":"string","description":"URL of the Musikcube Websocket (Metadata) server to connect to\n\nYou MUST have enabled 'metadata' server and set a password: https://github.com/clangen/musikcube/wiki/remote-api-documentation\n * musikcube -> settings -> server setup\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `7905`","examples":["ws://localhost:7905"],"default":"ws://localhost:7905"},"password":{"type":"string","description":"Password set in Musikcube https://github.com/clangen/musikcube/wiki/remote-api-documentation\n\n* musikcube -> settings -> server setup -> password"},"device_id":{"type":"string"}},"required":["password"],"title":"MusikcubeData"},"MusicCastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusicCastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MusicCastSourceConfig"},"MusicCastData":{"type":"object","properties":{"url":{"type":"string","description":"The host or URL of the YamahaExtendedControl endpoint to use","examples":[["192.168.0.101","http://192.168.0.101/YamahaExtendedControl"]]}},"required":["url"],"title":"MusicCastData"},"MPDSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPDData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/MPDSourceOptions"}},"required":["data","options"],"title":"MPDSourceConfig"},"MPDData":{"type":"object","properties":{"url":{"type":"string","description":"URL:PORT of the MPD server to connect to\n\nTo use this you must have TCP connections enabled for your MPD server https://mpd.readthedocs.io/en/stable/user.html#client-connections","examples":["localhost:6600"],"default":"localhost:6600"},"path":{"type":"string","description":"If using socket specify the path instead of url.\n\ntrailing `~` is replaced by your home directory"},"password":{"type":"string","description":"Password for the server, if set https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords"}},"title":"MPDData"},"MPDSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"MPDSourceOptions"},"VLCSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/VLCData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/VLCSourceOptions"}},"required":["data"],"title":"VLCSourceConfig"},"VLCData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL:PORT of the VLC server to connect to\n\nTo use this you must have the Web (http) interface module enabled and a password set https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc","examples":["localhost:8080"],"default":"localhost:8080"},"password":{"type":"string","description":"Password for the server"}},"required":["password"],"title":"VLCData"},"VLCSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"filenamePatterns":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A list of regular expressions to use to extract metadata (title, album, artist) from a filename\n\nUsed when VLC reports only the filename for the current audio track"},"logFilenamePatterns":{"type":"boolean","description":"Log to DEBUG when a filename-only track is matched or not matched by filenamePatterns","default":false},"dumpVlcMetadata":{"type":"boolean","description":"Dump all the metadata VLC reports for an audio track to DEBUG.\n\nUse this if reporting an issue with VLC not correctly capturing metadata for a track.","default":false}},"title":"VLCSourceOptions"},"IcecastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/IcecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/IcecastSourceOptions"}},"required":["data"],"title":"IcecastSourceConfig"},"IcecastData":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/definitions/IcecastSource"}},"icestatsEndpoint":{"type":"string"},"statsEndpoint":{"type":"string"},"nextsongsEndpoint":{"type":"string"},"sevenhtmlEndpoint":{"type":"string"},"icyMetaInt":{"type":"number"},"url":{"type":"string","description":"The Icecast stream URL"}},"required":["url"],"title":"IcecastData"},"IcecastSource":{"type":"string","enum":["icy","ogg","icestats","stats","sevenhtml","nextsongs"],"title":"IcecastSource"},"IcecastSourceOptions":{"type":"object","properties":{"systemScrobble":{"type":"boolean","description":"For Sources that support manual listening, should MS default to scrobbling when no user interaction has occurred?\n\nIf not specified MS will use a Source's specific behavior, see Source's documentation."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"IcecastSourceOptions"},"AzuracastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/AzuracastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"AzuracastSourceConfig"},"AzuracastData":{"type":"object","properties":{"url":{"type":"string","description":"Base URL of the Azuracast instance\n\nThis does NOT include the station. If a station is included it will be ignored. Use `station` field to specify station, if necessary","examples":["https://radio.mydomain.tld","http://localhost:80"]},"station":{"type":"string","description":"The specific station to monitor\n\nScrobbling will only occur if any of the monitor conditions are met AND the station is ONLINE.\n\nTo monitor multiple stations create a Source for each station.","examples":["my-station-1"]},"monitorWhenListeners":{"type":["boolean","number"],"description":"Only activate scrobble monitoring if station\n\n* `true` => has any current listeners\n* `number` => has EQUAL TO or MORE THAN X number of listeners"},"monitorWhenLive":{"type":"boolean","description":"Only activate scrobble monitoring if station has a live DJ/Streamer","default":true},"apiKey":{"type":"string","description":"API Key used to access data about private streams\n\nhttps://www.azuracast.com/docs/developers/apis/#api-authentication"}},"required":["url","station"],"title":"AzuracastData"},"KoitoSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KoitoSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `koito.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"KoitoSourceConfig"},"KoitoSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoSourceData"},"TealSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/TealSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/TealSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"Should always be `souce` when using Tealfm as a Source","default":"source","examples":["source"]}},"required":["data"],"title":"TealSourceConfig"},"TealSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealSourceData"},"TealSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealSourceOptions"},"RockskySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/RockskySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/RockskySourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `rocksky.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"RockskySourceConfig"},"RockskySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockskySourceData"},"RockskySourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockskySourceOptions"}}} \ No newline at end of file +{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"$ref":"#/definitions/SpotifySourceConfig"},{"$ref":"#/definitions/PlexSourceConfig"},{"$ref":"#/definitions/PlexApiSourceConfig"},{"$ref":"#/definitions/TautulliSourceConfig"},{"$ref":"#/definitions/DeezerSourceConfig"},{"$ref":"#/definitions/DeezerInternalSourceConfig"},{"$ref":"#/definitions/ListenbrainzEndpointSourceConfig"},{"$ref":"#/definitions/LastFMEndpointSourceConfig"},{"$ref":"#/definitions/SubSonicSourceConfig"},{"$ref":"#/definitions/JellySourceConfig"},{"$ref":"#/definitions/JellyApiSourceConfig"},{"$ref":"#/definitions/LastfmSourceConfig"},{"$ref":"#/definitions/YTMusicSourceConfig"},{"$ref":"#/definitions/MPRISSourceConfig"},{"$ref":"#/definitions/MopidySourceConfig"},{"$ref":"#/definitions/ListenBrainzSourceConfig"},{"$ref":"#/definitions/JRiverSourceConfig"},{"$ref":"#/definitions/KodiSourceConfig"},{"$ref":"#/definitions/WebScrobblerSourceConfig"},{"$ref":"#/definitions/ChromecastSourceConfig"},{"$ref":"#/definitions/MalojaSourceConfig"},{"$ref":"#/definitions/MusikcubeSourceConfig"},{"$ref":"#/definitions/MusicCastSourceConfig"},{"$ref":"#/definitions/MPDSourceConfig"},{"$ref":"#/definitions/VLCSourceConfig"},{"$ref":"#/definitions/IcecastSourceConfig"},{"$ref":"#/definitions/AzuracastSourceConfig"},{"$ref":"#/definitions/KoitoSourceConfig"},{"$ref":"#/definitions/TealSourceConfig"},{"$ref":"#/definitions/RockskySourceConfig"}],"definitions":{"SpotifySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SpotifySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"SpotifySourceConfig"},"SpotifySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)\n\nIt is unlikely you should need to change this unless you scrobble many very short tracks often\n\nReading:\n* https://developer.spotify.com/documentation/web-api/guides/rate-limits/\n* https://medium.com/mendix/limiting-your-amount-of-calls-in-mendix-most-of-the-time-rest-835dde55b10e\n * Rate limit may ~180 req/min\n* https://community.spotify.com/t5/Spotify-for-Developers/Web-API-ratelimit/m-p/5503150/highlight/true#M7930\n * Informally indicated as 20 req/sec? Probably for burstiness","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"spotify client id","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"clientSecret":{"type":"string","description":"spotify client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"spotify redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/callback","examples":["http://localhost:9078/callback"]}},"required":["clientId","clientSecret"],"title":"SpotifySourceData"},"CommonSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonSourceOptions"},"LogLevel":{"type":"string","enum":["silent","fatal","error","warn","info","log","verbose","debug"],"description":"Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.","title":"LogLevel"},"FileLogOptions":{"type":"object","properties":{"timestamp":{"type":"string","enum":["unix","iso","auto"],"description":"For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime","default":"auto"},"size":{"type":["number","string"],"description":"The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.","default":"10MB"},"frequency":{"anyOf":[{"type":"string","enum":["daily"]},{"type":"string","enum":["hourly"]},{"type":"number"}],"description":"The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.","default":"daily"},"path":{"anyOf":[{"type":"string"},{"$comment":"() => string"}],"description":"The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD","default":"./logs/app.log"},"level":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false}],"description":"Specify the minimum log level to output to rotating files. If `false` no log files will be created."}},"title":"FileLogOptions"},"ScrobbleThresholds":{"type":"object","properties":{"duration":{"type":["number","null"],"description":"The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.","default":240,"examples":[240]},"percent":{"type":["number","null"],"description":"The percentage (as an integer) of a track that should have been seen played before it should be scrobbled. Only used if the Source provides information about how long the track is.\n\nSet to null to disable.\n\nNOTE: This should be used with care when the Source is a \"polling\" type (has an 'interval' property). If the track is short and the interval is too high MS may ignore the track if percentage is high because it had not \"seen\" the track for long enough from first discovery, even if you have been playing the track for longer.","default":50,"examples":[50]}},"title":"ScrobbleThresholds"},"PlayTransformOptions":{"type":"object","properties":{"log":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["all"]}]},"preCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"compare":{"type":"object","properties":{"candidate":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"existing":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}}},"postCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}},"title":"PlayTransformOptions"},"PlayTransformPartsConfig":{"anyOf":[{"$ref":"#/definitions/PlayTransformPartsArray%3CSearchAndReplaceTerm%3E"},{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"}],"title":"PlayTransformPartsConfig"},"PlayTransformPartsArray":{"type":"array","items":{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"},"title":"PlayTransformPartsArray"},"PlayTransformParts":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"title":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"artists":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"album":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}}},"title":"PlayTransformParts"},"WhenConditionsConfig":{"$ref":"#/definitions/WhenConditions%3Cstring%3E","title":"WhenConditionsConfig"},"WhenConditions":{"type":"array","items":{"$ref":"#/definitions/WhenParts%3Cstring%3E"},"title":"WhenConditions"},"WhenParts":{"$ref":"#/definitions/PlayTransformPartsAtomic%3Cstring%3E","title":"WhenParts"},"PlayTransformPartsAtomic":{"type":"object","properties":{"title":{"type":"string"},"artists":{"type":"string"},"album":{"type":"string"}},"title":"PlayTransformPartsAtomic"},"SearchAndReplaceTerm":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/ConditionalSearchAndReplaceTerm"}],"title":"SearchAndReplaceTerm"},"ConditionalSearchAndReplaceTerm":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"search":{},"replace":{}},"required":["search","replace"],"title":"ConditionalSearchAndReplaceTerm"},"PlexSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"PlexSourceConfig"},"PlexSourceData":{"type":"object","properties":{"user":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"libraries":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of libraries to scrobble tracks from\n\nIf none are provided tracks from all libraries will be scrobbled","examples":[["Audio","Music"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName"]]}},"title":"PlexSourceData"},"PlexApiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/PlexApiOptions"}},"required":["data","options"],"title":"PlexApiSourceConfig"},"PlexApiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"token":{"type":"string"},"url":{"type":"string","description":"http(s)://HOST:PORT of the Plex server to connect to"},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"}},"required":["url"],"title":"PlexApiData"},"PlexApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"ignoreInvalidCert":{"type":"boolean","description":"Ignore invalid cert errors when connecting to Plex\n\nUseful for Plex servers using \"Required\" Secure Connections with self-signed certificates\n\nDo not enable unless you know you need this.","default":false}},"title":"PlexApiOptions"},"TautulliSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"TautulliSourceConfig"},"DeezerSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"DeezerSourceConfig"},"DeezerData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"deezer client id","examples":["a89cba1569901a0671d5a9875fed4be1"]},"clientSecret":{"type":"string","description":"deezer client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/deezer/callback","examples":["http://localhost:9078/deezer/callback"]}},"required":["clientId","clientSecret"],"title":"DeezerData"},"DeezerInternalSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerInternalData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/DeezerInternalSourceOptions"}},"required":["data"],"title":"DeezerInternalSourceConfig"},"DeezerInternalData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"arl":{"type":"string","description":"ARL retrieved from Deezer response header"},"userAgent":{"type":"string","description":"User agent","default":"Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0"}},"required":["arl"],"title":"DeezerInternalData"},"DeezerInternalSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"fuzzyDiscoveryIgnore":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["aggressive"]}]}},"title":"DeezerInternalSourceOptions"},"ListenbrainzEndpointSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenbrainzEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"ListenbrainzEndpointSourceConfig"},"ListenbrainzEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Listenbrainz endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/listenbrainz/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/listenbrainz/originb\n\nIf no slug is found from an extension's incoming webhook event the first Listenbrainz source without a slug will be used"},"token":{"type":["string","null"],"description":"If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source\n\nSee: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#add-the-user-token-to-your-requests"}},"title":"ListenbrainzEndpointData"},"LastFMEndpointSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFMEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"LastFMEndpointSourceConfig"},"LastFMEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Last.fm endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/lastfm/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/lastfm/originb\n\nIf no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used"}},"title":"LastFMEndpointData"},"SubSonicSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SubsonicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"SubSonicSourceConfig"},"SubsonicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the subsonic media server to query","examples":["http://airsonic.local"]},"user":{"type":"string","description":"Username to login to the server with","examples":[["MyUser"]]},"password":{"type":"string","description":"Password for the user to login to the server with","examples":["MyPassword"]},"ignoreTlsErrors":{"type":"boolean","description":"If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection\n\nWARNING: This should be used with caution as your traffic may not be encrypted.","default":false},"legacyAuthentication":{"type":"boolean","description":"Older Subsonic versions, and some badly implemented servers (Nextcloud), use legacy authentication which sends your password in CLEAR TEXT. This is less secure than the newer, recommended hashing authentication method but in some cases it is needed. See \"Authentication\" section here => https://www.subsonic.org/pages/api.jsp\n\nIf this option is not specified it will be turned on if the subsonic server responds with error code 41 \"Token authentication not supported for LDAP users.\" -- See Error Handling section => https://www.subsonic.org/pages/api.jsp","default":false},"usersAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf undefined or an empty string/list MS will scrobble activity from all users"}},"required":["url","user","password"],"title":"SubsonicData"},"JellySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"JellySourceConfig"},"JellyData":{"type":"object","properties":{"users":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName1"]]}},"title":"JellyData"},"JellyApiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/JellyApiOptions"}},"required":["data","options"],"title":"JellyApiSourceConfig"},"JellyApiData":{"type":"object","properties":{"url":{"type":"string","description":"HOST:PORT of the Jellyfin server to connect to"},"user":{"type":"string","description":"The username of the user to authenticate for or track scrobbles for"},"password":{"type":"string","description":"Password of the username to authenticate for\n\nRequired if `apiKey` is not provided."},"apiKey":{"type":"string","description":"API Key to authenticate with.\n\nRequired if `password` is not provided."},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"},"additionalAllowedLibraryTypes":{"type":"array","items":{},"description":"Allow MS to scrobble audio media in libraries classified other than 'music'\n\n`librariesAllow` will achieve the same result as this but this is more convenient if you do not want to explicitly list every library name or are only using `librariesBlock`"},"allowUnknown":{"type":"boolean","description":"Force media with a type of \"Unknown\" to be counted as Audio","default":false},"frontendUrlOverride":{"type":"string","description":"HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from)\nIf unspecified it will use the normal server HOST and PORT from the `url`\nNecessary if you are using a reverse proxy or other network configuration that prevents the frontend from accessing the server directly\n\nENV: JELLYFIN_FRONTEND_URL_OVERRIDE"}},"required":["url","user"],"title":"JellyApiData"},"JellyApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"JellyApiOptions"},"LastfmSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFmSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"LastfmSourceConfig"},"LastFmSourceData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]},"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"}},"required":["apiKey","secret"],"title":"LastFmSourceData"},"YTMusicSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/YTMusicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"type":"object","properties":{"logAuth":{"type":"boolean","description":"When true MS will log to DEBUG all of the credentials data it receives from YTM"},"logDiff":{"type":"boolean","description":"Always log history diff\n\nBy default MS will log to `WARN` if history diff is inconsistent but does not log if diff is expected (on new tracks found)\nSet this to `true` to ALWAYS log diff on new tracks. Expected diffs will log to `DEBUG` and inconsistent diffs will continue to log to `WARN`","default":false},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}}}},"title":"YTMusicSourceConfig"},"YTMusicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"cookie":{"type":"string","description":"The cookie retrieved from the Request Headers of music.youtube.com after logging in.\n\nSee https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers for how to retrieve this value.","examples":["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]},"clientId":{"type":"string","description":"Google Cloud Console project OAuth Client ID\n\nGenerated from a custom OAuth Client, see docs"},"clientSecret":{"type":"string","description":"Google Cloud Console project OAuth Client Secret\n\nGenerated from a custom OAuth Client, see docs"},"redirectUri":{"type":"string","description":"Google Cloud Console project OAuth Client Authorized redirect URI\n\nGenerated from a custom OAuth Client, see docs. multi-scrobbler will generate a default based on BASE_URL.\nOnly specify this if the default does not work for you."},"innertubeOptions":{"$ref":"#/definitions/InnertubeOptions","description":"Additional options for authorization and tailoring YTM client"}},"title":"YTMusicData"},"InnertubeOptions":{"type":"object","properties":{"po_token":{"type":"string","description":"Proof of Origin token\n\nMay be required if YTM starts returning 403"},"visitor_data":{"type":"string","description":"Visitor ID value found in VISITOR_INFO1_LIVE or visitorData cookie\n\nMay be required if YTM starts returning 403"},"account_index":{"type":"number","description":"If account login results in being able to choose multiple account, use a zero-based index to choose which one to monitor","examples":[0,1]},"location":{"type":"string"},"lang":{"type":"string"},"generate_session_locally":{"type":"boolean"},"device_category":{"type":"string"},"client_type":{"type":"string"},"timezone":{"type":"string"}},"title":"InnertubeOptions"},"MPRISSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPRISData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MPRISSourceConfig"},"MPRISData":{"type":"object","properties":{"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any players that START WITH these values, case-insensitive","examples":[["spotify","vlc"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","vlc"]]}},"title":"MPRISData"},"MopidySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MopidyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MopidySourceConfig"},"MopidyData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Mopidy HTTP server to connect to\n\nYou MUST have Mopidy-HTTP extension enabled: https://mopidy.com/ext/http\n\nmulti-scrobbler connects to the WebSocket endpoint that ultimately looks like this => `ws://localhost:6680/mopidy/ws/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `6680`\n* Path => `/mopidy/ws/`","examples":["ws://localhost:6680/mopidy/ws/"],"default":"ws://localhost:6680/mopidy/ws/"},"uriBlacklist":{"type":"array","items":{"type":"string"},"description":"Do not scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Don't scrobble tracks from soundcloud by adding 'soundcloud' to this list.\n\nList is ignored if uriWhitelist is used."},"uriWhitelist":{"type":"array","items":{"type":"string"},"description":"Only scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Only scrobble tracks from soundcloud by adding 'soundcloud' to this list."},"albumBlacklist":{"type":"array","items":{"type":"string"},"description":"Remove album data that matches any case-insensitive string from this list when scrobbling,\n\nFor certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use \"Soundcloud\" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake name to this list. Multi-scrobbler will still scrobble the track, just without the bad data.","examples":[["Soundcloud","Mixcloud"]],"default":["Soundcloud"]}},"title":"MopidyData"},"ListenBrainzSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenBrainzSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `listenbrainz.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"ListenBrainzSourceConfig"},"ListenBrainzSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzSourceData"},"JRiverSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JRiverData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"JRiverSourceConfig"},"JRiverData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the JRiver HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:52199/MCWS/v1/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `52199`\n* Path => `/MCWS/v1/`","examples":["http://localhost:52199/MCWS/v1/"],"default":"http://localhost:52199/MCWS/v1/"},"username":{"type":"string","description":"If you have enabled authentication, the username you set"},"password":{"type":"string","description":"If you have enabled authentication, the password you set"}},"required":["url"],"title":"JRiverData"},"KodiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KodiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"KodiSourceConfig"},"KodiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Kodi HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:8080/jsonrpc`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `8080`\n* Path => `/jsonrpc`","examples":["http://localhost:8080/jsonrpc"],"default":"http://localhost:8080/jsonrpc"},"username":{"type":"string","description":"The username set for Remote Control via Web Sever"},"password":{"type":"string","description":"The password set for Remote Control via Web Sever"}},"required":["url","username","password"],"title":"KodiData"},"WebScrobblerSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/WebScrobblerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"WebScrobblerSourceConfig"},"WebScrobblerData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIn WebScrobbler's Webhook you must set an 'API URL'. All MS WebScrobbler sources must start like:\n\nhttp://localhost:9078/api/webscrobbler\n\nIf you are using multiple WebScrobbler sources (scrobbles for many users) you must use a slug to match Sources with each users extension.\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/webscrobbler/usera\n* slug: 'userb' => API URL: http://localhost:9078/api/webscrobbler/userb\n\nIf no slug is found from an extension's incoming webhook event the first WebScrobbler source without a slug will be used"},"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Block scrobbling from specific WebScrobbler Connectors","examples":[["youtube"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only allow scrobbling from specific WebScrobbler Connectors","examples":[["mixcloud","soundcloud","bandcamp"]]}},"title":"WebScrobblerData"},"ChromecastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ChromecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"ChromecastSourceConfig"},"ChromecastData":{"type":"object","properties":{"blacklistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"whitelistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any cast device that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"blacklistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any application that START WITH these values, case-insensitive","examples":[["spotify","pandora"]]},"whitelistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any application that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","pandora"]]},"useAvahi":{"type":"boolean","description":"Try to use Avahi and avahi-browse to resolve mDNS devices instead of native mDNS querying\n\nUseful for docker (alpine) container where mDNS resolution is not yet supported. Avahi socket must be exposed to the container and avahi-tools must be installed.","default":false},"useAutoDiscovery":{"type":"boolean","description":"Use mDNS to discovery Google Cast devices on your next automatically?\n\nIf not explicitly set then it is TRUE if `devices` is not set"},"devices":{"type":"array","items":{"$ref":"#/definitions/ChromecastDeviceInfo"},"description":"A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set"},"allowUnknownMedia":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Chromecast Apps report a \"media type\" in the status info returned for whatever is currently playing\n\n* If set to TRUE then Music AND Generic/Unknown media will be tracked for ALL APPS\n* If set to FALSE then only media explicitly typed as Music will be tracked for ALL APPS\n* If set to a list then only Apps whose name contain one of these values, case-insensitive, will have Music AND Generic/Unknown tracked\n\nSee https://developers.google.com/cast/docs/media/messages#MediaInformation \"metadata\" property","default":false},"forceMediaRecognitionOn":{"type":"array","items":{"type":"string"},"description":"Media provided by any App whose name is listed here will ALWAYS be tracked, regardless of the \"media type\" reported\n\nApps will be recognized if they CONTAIN any of these values, case-insensitive"}},"title":"ChromecastData"},"ChromecastDeviceInfo":{"type":"object","properties":{"name":{"type":"string","description":"A friendly name to identify this device","examples":["MySmartTV"]},"address":{"type":"string","description":"The IP address of the device","examples":["192.168.0.115"]}},"required":["name","address"],"title":"ChromecastDeviceInfo"},"MalojaSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MalojaSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `maloja.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"MalojaSourceConfig"},"MalojaSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaSourceData"},"MusikcubeSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusikcubeData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MusikcubeSourceConfig"},"MusikcubeData":{"type":"object","properties":{"url":{"type":"string","description":"URL of the Musikcube Websocket (Metadata) server to connect to\n\nYou MUST have enabled 'metadata' server and set a password: https://github.com/clangen/musikcube/wiki/remote-api-documentation\n * musikcube -> settings -> server setup\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `7905`","examples":["ws://localhost:7905"],"default":"ws://localhost:7905"},"password":{"type":"string","description":"Password set in Musikcube https://github.com/clangen/musikcube/wiki/remote-api-documentation\n\n* musikcube -> settings -> server setup -> password"},"device_id":{"type":"string"}},"required":["password"],"title":"MusikcubeData"},"MusicCastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusicCastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MusicCastSourceConfig"},"MusicCastData":{"type":"object","properties":{"url":{"type":"string","description":"The host or URL of the YamahaExtendedControl endpoint to use","examples":[["192.168.0.101","http://192.168.0.101/YamahaExtendedControl"]]}},"required":["url"],"title":"MusicCastData"},"MPDSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPDData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/MPDSourceOptions"}},"required":["data","options"],"title":"MPDSourceConfig"},"MPDData":{"type":"object","properties":{"url":{"type":"string","description":"URL:PORT of the MPD server to connect to\n\nTo use this you must have TCP connections enabled for your MPD server https://mpd.readthedocs.io/en/stable/user.html#client-connections","examples":["localhost:6600"],"default":"localhost:6600"},"path":{"type":"string","description":"If using socket specify the path instead of url.\n\ntrailing `~` is replaced by your home directory"},"password":{"type":"string","description":"Password for the server, if set https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords"}},"title":"MPDData"},"MPDSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"MPDSourceOptions"},"VLCSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/VLCData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/VLCSourceOptions"}},"required":["data"],"title":"VLCSourceConfig"},"VLCData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL:PORT of the VLC server to connect to\n\nTo use this you must have the Web (http) interface module enabled and a password set https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc","examples":["localhost:8080"],"default":"localhost:8080"},"password":{"type":"string","description":"Password for the server"}},"required":["password"],"title":"VLCData"},"VLCSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"filenamePatterns":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A list of regular expressions to use to extract metadata (title, album, artist) from a filename\n\nUsed when VLC reports only the filename for the current audio track"},"logFilenamePatterns":{"type":"boolean","description":"Log to DEBUG when a filename-only track is matched or not matched by filenamePatterns","default":false},"dumpVlcMetadata":{"type":"boolean","description":"Dump all the metadata VLC reports for an audio track to DEBUG.\n\nUse this if reporting an issue with VLC not correctly capturing metadata for a track.","default":false}},"title":"VLCSourceOptions"},"IcecastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/IcecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/IcecastSourceOptions"}},"required":["data"],"title":"IcecastSourceConfig"},"IcecastData":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/definitions/IcecastSource"}},"icestatsEndpoint":{"type":"string"},"statsEndpoint":{"type":"string"},"nextsongsEndpoint":{"type":"string"},"sevenhtmlEndpoint":{"type":"string"},"icyMetaInt":{"type":"number"},"url":{"type":"string","description":"The Icecast stream URL"}},"required":["url"],"title":"IcecastData"},"IcecastSource":{"type":"string","enum":["icy","ogg","icestats","stats","sevenhtml","nextsongs"],"title":"IcecastSource"},"IcecastSourceOptions":{"type":"object","properties":{"systemScrobble":{"type":"boolean","description":"For Sources that support manual listening, should MS default to scrobbling when no user interaction has occurred?\n\nIf not specified MS will use a Source's specific behavior, see Source's documentation."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"IcecastSourceOptions"},"AzuracastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/AzuracastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"AzuracastSourceConfig"},"AzuracastData":{"type":"object","properties":{"url":{"type":"string","description":"Base URL of the Azuracast instance\n\nThis does NOT include the station. If a station is included it will be ignored. Use `station` field to specify station, if necessary","examples":["https://radio.mydomain.tld","http://localhost:80"]},"station":{"type":"string","description":"The specific station to monitor\n\nScrobbling will only occur if any of the monitor conditions are met AND the station is ONLINE.\n\nTo monitor multiple stations create a Source for each station.","examples":["my-station-1"]},"monitorWhenListeners":{"type":["boolean","number"],"description":"Only activate scrobble monitoring if station\n\n* `true` => has any current listeners\n* `number` => has EQUAL TO or MORE THAN X number of listeners"},"monitorWhenLive":{"type":"boolean","description":"Only activate scrobble monitoring if station has a live DJ/Streamer","default":true},"apiKey":{"type":"string","description":"API Key used to access data about private streams\n\nhttps://www.azuracast.com/docs/developers/apis/#api-authentication"}},"required":["url","station"],"title":"AzuracastData"},"KoitoSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KoitoSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `koito.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"KoitoSourceConfig"},"KoitoSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoSourceData"},"TealSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/TealSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/TealSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"Should always be `souce` when using Tealfm as a Source","default":"source","examples":["source"]}},"required":["data"],"title":"TealSourceConfig"},"TealSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealSourceData"},"TealSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealSourceOptions"},"RockskySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/RockskySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/RockskySourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `rocksky.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"RockskySourceConfig"},"RockskySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockskySourceData"},"RockskySourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockskySourceOptions"}}} diff --git a/src/backend/common/schema/aio-source.json b/src/backend/common/schema/aio-source.json index de2be93a..6225c7d6 100644 --- a/src/backend/common/schema/aio-source.json +++ b/src/backend/common/schema/aio-source.json @@ -1 +1 @@ -{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"sourceDefaults":{"$ref":"#/definitions/SourceRetryOptions"},"sources":{"type":"array","items":{"$ref":"#/definitions/SourceAIOConfig"}}},"definitions":{"SourceRetryOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]}},"title":"SourceRetryOptions"},"SourceAIOConfig":{"anyOf":[{"$ref":"#/definitions/SpotifySourceAIOConfig"},{"$ref":"#/definitions/PlexSourceAIOConfig"},{"$ref":"#/definitions/PlexApiSourceAIOConfig"},{"$ref":"#/definitions/TautulliSourceAIOConfig"},{"$ref":"#/definitions/DeezerSourceAIOConfig"},{"$ref":"#/definitions/DeezerInternalAIOConfig"},{"$ref":"#/definitions/ListenbrainzEndpointSourceAIOConfig"},{"$ref":"#/definitions/LastFMEndpointSourceAIOConfig"},{"$ref":"#/definitions/SubsonicSourceAIOConfig"},{"$ref":"#/definitions/JellySourceAIOConfig"},{"$ref":"#/definitions/JellyApiSourceAIOConfig"},{"$ref":"#/definitions/LastFmSouceAIOConfig"},{"$ref":"#/definitions/YTMusicSourceAIOConfig"},{"$ref":"#/definitions/MPRISSourceAIOConfig"},{"$ref":"#/definitions/MopidySourceAIOConfig"},{"$ref":"#/definitions/ListenBrainzSourceAIOConfig"},{"$ref":"#/definitions/JRiverSourceAIOConfig"},{"$ref":"#/definitions/KodiSourceAIOConfig"},{"$ref":"#/definitions/WebScrobblerSourceAIOConfig"},{"$ref":"#/definitions/ChromecastSourceAIOConfig"},{"$ref":"#/definitions/MalojaSourceAIOConfig"},{"$ref":"#/definitions/MusikcubeSourceAIOConfig"},{"$ref":"#/definitions/MusicCastSourceAIOConfig"},{"$ref":"#/definitions/MPDSourceAIOConfig"},{"$ref":"#/definitions/VLCSourceAIOConfig"},{"$ref":"#/definitions/IcecastSourceAIOConfig"},{"$ref":"#/definitions/AzuracastSourceAIOConfig"},{"$ref":"#/definitions/KoitoSourceAIOConfig"},{"$ref":"#/definitions/TealSourceAIOConfig"},{"$ref":"#/definitions/RockskySourceAIOConfig"}],"title":"SourceAIOConfig"},"SpotifySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SpotifySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["spotify"]}},"required":["data","type"],"title":"SpotifySourceAIOConfig"},"SpotifySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)\n\nIt is unlikely you should need to change this unless you scrobble many very short tracks often\n\nReading:\n* https://developer.spotify.com/documentation/web-api/guides/rate-limits/\n* https://medium.com/mendix/limiting-your-amount-of-calls-in-mendix-most-of-the-time-rest-835dde55b10e\n * Rate limit may ~180 req/min\n* https://community.spotify.com/t5/Spotify-for-Developers/Web-API-ratelimit/m-p/5503150/highlight/true#M7930\n * Informally indicated as 20 req/sec? Probably for burstiness","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"spotify client id","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"clientSecret":{"type":"string","description":"spotify client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"spotify redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/callback","examples":["http://localhost:9078/callback"]}},"required":["clientId","clientSecret"],"title":"SpotifySourceData"},"CommonSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonSourceOptions"},"LogLevel":{"type":"string","enum":["silent","fatal","error","warn","info","log","verbose","debug"],"description":"Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.","title":"LogLevel"},"FileLogOptions":{"type":"object","properties":{"timestamp":{"type":"string","enum":["unix","iso","auto"],"description":"For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime","default":"auto"},"size":{"type":["number","string"],"description":"The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.","default":"10MB"},"frequency":{"anyOf":[{"type":"string","enum":["daily"]},{"type":"string","enum":["hourly"]},{"type":"number"}],"description":"The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.","default":"daily"},"path":{"anyOf":[{"type":"string"},{"$comment":"() => string"}],"description":"The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD","default":"./logs/app.log"},"level":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false}],"description":"Specify the minimum log level to output to rotating files. If `false` no log files will be created."}},"title":"FileLogOptions"},"ScrobbleThresholds":{"type":"object","properties":{"duration":{"type":["number","null"],"description":"The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.","default":240,"examples":[240]},"percent":{"type":["number","null"],"description":"The percentage (as an integer) of a track that should have been seen played before it should be scrobbled. Only used if the Source provides information about how long the track is.\n\nSet to null to disable.\n\nNOTE: This should be used with care when the Source is a \"polling\" type (has an 'interval' property). If the track is short and the interval is too high MS may ignore the track if percentage is high because it had not \"seen\" the track for long enough from first discovery, even if you have been playing the track for longer.","default":50,"examples":[50]}},"title":"ScrobbleThresholds"},"PlayTransformOptions":{"type":"object","properties":{"log":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["all"]}]},"preCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"compare":{"type":"object","properties":{"candidate":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"existing":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}}},"postCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}},"title":"PlayTransformOptions"},"PlayTransformPartsConfig":{"anyOf":[{"$ref":"#/definitions/PlayTransformPartsArray%3CSearchAndReplaceTerm%3E"},{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"}],"title":"PlayTransformPartsConfig"},"PlayTransformPartsArray":{"type":"array","items":{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"},"title":"PlayTransformPartsArray"},"PlayTransformParts":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"title":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"artists":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"album":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}}},"title":"PlayTransformParts"},"WhenConditionsConfig":{"$ref":"#/definitions/WhenConditions%3Cstring%3E","title":"WhenConditionsConfig"},"WhenConditions":{"type":"array","items":{"$ref":"#/definitions/WhenParts%3Cstring%3E"},"title":"WhenConditions"},"WhenParts":{"$ref":"#/definitions/PlayTransformPartsAtomic%3Cstring%3E","title":"WhenParts"},"PlayTransformPartsAtomic":{"type":"object","properties":{"title":{"type":"string"},"artists":{"type":"string"},"album":{"type":"string"}},"title":"PlayTransformPartsAtomic"},"SearchAndReplaceTerm":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/ConditionalSearchAndReplaceTerm"}],"title":"SearchAndReplaceTerm"},"ConditionalSearchAndReplaceTerm":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"search":{},"replace":{}},"required":["search","replace"],"title":"ConditionalSearchAndReplaceTerm"},"PlexSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","type"],"title":"PlexSourceAIOConfig"},"PlexSourceData":{"type":"object","properties":{"user":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"libraries":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of libraries to scrobble tracks from\n\nIf none are provided tracks from all libraries will be scrobbled","examples":[["Audio","Music"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName"]]}},"title":"PlexSourceData"},"PlexApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/PlexApiOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","options","type"],"title":"PlexApiSourceAIOConfig"},"PlexApiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"token":{"type":"string"},"url":{"type":"string","description":"http(s)://HOST:PORT of the Plex server to connect to"},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"}},"required":["url"],"title":"PlexApiData"},"PlexApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"ignoreInvalidCert":{"type":"boolean","description":"Ignore invalid cert errors when connecting to Plex\n\nUseful for Plex servers using \"Required\" Secure Connections with self-signed certificates\n\nDo not enable unless you know you need this.","default":false}},"title":"PlexApiOptions"},"TautulliSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["tautulli"]}},"required":["data","type"],"title":"TautulliSourceAIOConfig"},"DeezerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerSourceAIOConfig"},"DeezerData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"deezer client id","examples":["a89cba1569901a0671d5a9875fed4be1"]},"clientSecret":{"type":"string","description":"deezer client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/deezer/callback","examples":["http://localhost:9078/deezer/callback"]}},"required":["clientId","clientSecret"],"title":"DeezerData"},"DeezerInternalAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerInternalData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/DeezerInternalSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerInternalAIOConfig"},"DeezerInternalData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"arl":{"type":"string","description":"ARL retrieved from Deezer response header"},"userAgent":{"type":"string","description":"User agent","default":"Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0"}},"required":["arl"],"title":"DeezerInternalData"},"DeezerInternalSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"fuzzyDiscoveryIgnore":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["aggressive"]}]}},"title":"DeezerInternalSourceOptions"},"ListenbrainzEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenbrainzEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlz"]}},"required":["type"],"title":"ListenbrainzEndpointSourceAIOConfig"},"ListenbrainzEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Listenbrainz endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/listenbrainz/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/listenbrainz/originb\n\nIf no slug is found from an extension's incoming webhook event the first Listenbrainz source without a slug will be used"},"token":{"type":["string","null"],"description":"If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source\n\nSee: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#add-the-user-token-to-your-requests"}},"title":"ListenbrainzEndpointData"},"LastFMEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFMEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlfm"]}},"required":["type"],"title":"LastFMEndpointSourceAIOConfig"},"LastFMEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Last.fm endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/lastfm/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/lastfm/originb\n\nIf no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used"}},"title":"LastFMEndpointData"},"SubsonicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SubsonicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["subsonic"]}},"required":["data","type"],"title":"SubsonicSourceAIOConfig"},"SubsonicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the subsonic media server to query","examples":["http://airsonic.local"]},"user":{"type":"string","description":"Username to login to the server with","examples":[["MyUser"]]},"password":{"type":"string","description":"Password for the user to login to the server with","examples":["MyPassword"]},"ignoreTlsErrors":{"type":"boolean","description":"If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection\n\nWARNING: This should be used with caution as your traffic may not be encrypted.","default":false},"legacyAuthentication":{"type":"boolean","description":"Older Subsonic versions, and some badly implemented servers (Nextcloud), use legacy authentication which sends your password in CLEAR TEXT. This is less secure than the newer, recommended hashing authentication method but in some cases it is needed. See \"Authentication\" section here => https://www.subsonic.org/pages/api.jsp\n\nIf this option is not specified it will be turned on if the subsonic server responds with error code 41 \"Token authentication not supported for LDAP users.\" -- See Error Handling section => https://www.subsonic.org/pages/api.jsp","default":false},"usersAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf undefined or an empty string/list MS will scrobble activity from all users"}},"required":["url","user","password"],"title":"SubsonicData"},"JellySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","type"],"title":"JellySourceAIOConfig"},"JellyData":{"type":"object","properties":{"users":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName1"]]}},"title":"JellyData"},"JellyApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/JellyApiOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","options","type"],"title":"JellyApiSourceAIOConfig"},"JellyApiData":{"type":"object","properties":{"url":{"type":"string","description":"HOST:PORT of the Jellyfin server to connect to"},"user":{"type":"string","description":"The username of the user to authenticate for or track scrobbles for"},"password":{"type":"string","description":"Password of the username to authenticate for\n\nRequired if `apiKey` is not provided."},"apiKey":{"type":"string","description":"API Key to authenticate with.\n\nRequired if `password` is not provided."},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"},"additionalAllowedLibraryTypes":{"type":"array","items":{},"description":"Allow MS to scrobble audio media in libraries classified other than 'music'\n\n`librariesAllow` will achieve the same result as this but this is more convenient if you do not want to explicitly list every library name or are only using `librariesBlock`"},"allowUnknown":{"type":"boolean","description":"Force media with a type of \"Unknown\" to be counted as Audio","default":false},"frontendUrlOverride":{"type":"string","description":"HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from)\nIf unspecified it will use the normal server HOST and PORT from the `url`\nNecessary if you are using a reverse proxy or other network configuration that prevents the frontend from accessing the server directly\n\nENV: JELLYFIN_FRONTEND_URL_OVERRIDE"}},"required":["url","user"],"title":"JellyApiData"},"JellyApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"JellyApiOptions"},"LastFmSouceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFmSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["lastfm"]}},"required":["data","type"],"title":"LastFmSouceAIOConfig"},"LastFmSourceData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]},"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"}},"required":["apiKey","secret"],"title":"LastFmSourceData"},"YTMusicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/YTMusicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"type":"object","properties":{"logAuth":{"type":"boolean","description":"When true MS will log to DEBUG all of the credentials data it receives from YTM"},"logDiff":{"type":"boolean","description":"Always log history diff\n\nBy default MS will log to `WARN` if history diff is inconsistent but does not log if diff is expected (on new tracks found)\nSet this to `true` to ALWAYS log diff on new tracks. Expected diffs will log to `DEBUG` and inconsistent diffs will continue to log to `WARN`","default":false},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}}},"type":{"type":"string","enum":["ytmusic"]}},"required":["type"],"title":"YTMusicSourceAIOConfig"},"YTMusicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"cookie":{"type":"string","description":"The cookie retrieved from the Request Headers of music.youtube.com after logging in.\n\nSee https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers for how to retrieve this value.","examples":["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]},"clientId":{"type":"string","description":"Google Cloud Console project OAuth Client ID\n\nGenerated from a custom OAuth Client, see docs"},"clientSecret":{"type":"string","description":"Google Cloud Console project OAuth Client Secret\n\nGenerated from a custom OAuth Client, see docs"},"redirectUri":{"type":"string","description":"Google Cloud Console project OAuth Client Authorized redirect URI\n\nGenerated from a custom OAuth Client, see docs. multi-scrobbler will generate a default based on BASE_URL.\nOnly specify this if the default does not work for you."},"innertubeOptions":{"$ref":"#/definitions/InnertubeOptions","description":"Additional options for authorization and tailoring YTM client"}},"title":"YTMusicData"},"InnertubeOptions":{"type":"object","properties":{"po_token":{"type":"string","description":"Proof of Origin token\n\nMay be required if YTM starts returning 403"},"visitor_data":{"type":"string","description":"Visitor ID value found in VISITOR_INFO1_LIVE or visitorData cookie\n\nMay be required if YTM starts returning 403"},"account_index":{"type":"number","description":"If account login results in being able to choose multiple account, use a zero-based index to choose which one to monitor","examples":[0,1]},"location":{"type":"string"},"lang":{"type":"string"},"generate_session_locally":{"type":"boolean"},"device_category":{"type":"string"},"client_type":{"type":"string"},"timezone":{"type":"string"}},"title":"InnertubeOptions"},"MPRISSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPRISData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mpris"]}},"required":["data","type"],"title":"MPRISSourceAIOConfig"},"MPRISData":{"type":"object","properties":{"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any players that START WITH these values, case-insensitive","examples":[["spotify","vlc"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","vlc"]]}},"title":"MPRISData"},"MopidySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MopidyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mopidy"]}},"required":["data","type"],"title":"MopidySourceAIOConfig"},"MopidyData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Mopidy HTTP server to connect to\n\nYou MUST have Mopidy-HTTP extension enabled: https://mopidy.com/ext/http\n\nmulti-scrobbler connects to the WebSocket endpoint that ultimately looks like this => `ws://localhost:6680/mopidy/ws/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `6680`\n* Path => `/mopidy/ws/`","examples":["ws://localhost:6680/mopidy/ws/"],"default":"ws://localhost:6680/mopidy/ws/"},"uriBlacklist":{"type":"array","items":{"type":"string"},"description":"Do not scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Don't scrobble tracks from soundcloud by adding 'soundcloud' to this list.\n\nList is ignored if uriWhitelist is used."},"uriWhitelist":{"type":"array","items":{"type":"string"},"description":"Only scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Only scrobble tracks from soundcloud by adding 'soundcloud' to this list."},"albumBlacklist":{"type":"array","items":{"type":"string"},"description":"Remove album data that matches any case-insensitive string from this list when scrobbling,\n\nFor certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use \"Soundcloud\" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake name to this list. Multi-scrobbler will still scrobble the track, just without the bad data.","examples":[["Soundcloud","Mixcloud"]],"default":["Soundcloud"]}},"title":"MopidyData"},"ListenBrainzSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenBrainzSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `listenbrainz.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["listenbrainz"]}},"required":["data","type"],"title":"ListenBrainzSourceAIOConfig"},"ListenBrainzSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzSourceData"},"JRiverSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JRiverData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jriver"]}},"required":["data","type"],"title":"JRiverSourceAIOConfig"},"JRiverData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the JRiver HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:52199/MCWS/v1/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `52199`\n* Path => `/MCWS/v1/`","examples":["http://localhost:52199/MCWS/v1/"],"default":"http://localhost:52199/MCWS/v1/"},"username":{"type":"string","description":"If you have enabled authentication, the username you set"},"password":{"type":"string","description":"If you have enabled authentication, the password you set"}},"required":["url"],"title":"JRiverData"},"KodiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KodiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["kodi"]}},"required":["data","type"],"title":"KodiSourceAIOConfig"},"KodiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Kodi HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:8080/jsonrpc`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `8080`\n* Path => `/jsonrpc`","examples":["http://localhost:8080/jsonrpc"],"default":"http://localhost:8080/jsonrpc"},"username":{"type":"string","description":"The username set for Remote Control via Web Sever"},"password":{"type":"string","description":"The password set for Remote Control via Web Sever"}},"required":["url","username","password"],"title":"KodiData"},"WebScrobblerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/WebScrobblerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["webscrobbler"]}},"required":["type"],"title":"WebScrobblerSourceAIOConfig"},"WebScrobblerData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIn WebScrobbler's Webhook you must set an 'API URL'. All MS WebScrobbler sources must start like:\n\nhttp://localhost:9078/api/webscrobbler\n\nIf you are using multiple WebScrobbler sources (scrobbles for many users) you must use a slug to match Sources with each users extension.\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/webscrobbler/usera\n* slug: 'userb' => API URL: http://localhost:9078/api/webscrobbler/userb\n\nIf no slug is found from an extension's incoming webhook event the first WebScrobbler source without a slug will be used"},"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Block scrobbling from specific WebScrobbler Connectors","examples":[["youtube"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only allow scrobbling from specific WebScrobbler Connectors","examples":[["mixcloud","soundcloud","bandcamp"]]}},"title":"WebScrobblerData"},"ChromecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ChromecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["chromecast"]}},"required":["data","type"],"title":"ChromecastSourceAIOConfig"},"ChromecastData":{"type":"object","properties":{"blacklistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"whitelistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any cast device that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"blacklistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any application that START WITH these values, case-insensitive","examples":[["spotify","pandora"]]},"whitelistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any application that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","pandora"]]},"useAvahi":{"type":"boolean","description":"Try to use Avahi and avahi-browse to resolve mDNS devices instead of native mDNS querying\n\nUseful for docker (alpine) container where mDNS resolution is not yet supported. Avahi socket must be exposed to the container and avahi-tools must be installed.","default":false},"useAutoDiscovery":{"type":"boolean","description":"Use mDNS to discovery Google Cast devices on your next automatically?\n\nIf not explicitly set then it is TRUE if `devices` is not set"},"devices":{"type":"array","items":{"$ref":"#/definitions/ChromecastDeviceInfo"},"description":"A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set"},"allowUnknownMedia":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Chromecast Apps report a \"media type\" in the status info returned for whatever is currently playing\n\n* If set to TRUE then Music AND Generic/Unknown media will be tracked for ALL APPS\n* If set to FALSE then only media explicitly typed as Music will be tracked for ALL APPS\n* If set to a list then only Apps whose name contain one of these values, case-insensitive, will have Music AND Generic/Unknown tracked\n\nSee https://developers.google.com/cast/docs/media/messages#MediaInformation \"metadata\" property","default":false},"forceMediaRecognitionOn":{"type":"array","items":{"type":"string"},"description":"Media provided by any App whose name is listed here will ALWAYS be tracked, regardless of the \"media type\" reported\n\nApps will be recognized if they CONTAIN any of these values, case-insensitive"}},"title":"ChromecastData"},"ChromecastDeviceInfo":{"type":"object","properties":{"name":{"type":"string","description":"A friendly name to identify this device","examples":["MySmartTV"]},"address":{"type":"string","description":"The IP address of the device","examples":["192.168.0.115"]}},"required":["name","address"],"title":"ChromecastDeviceInfo"},"MalojaSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MalojaSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `maloja.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["maloja"]}},"required":["data","type"],"title":"MalojaSourceAIOConfig"},"MalojaSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaSourceData"},"MusikcubeSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusikcubeData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musikcube"]}},"required":["data","type"],"title":"MusikcubeSourceAIOConfig"},"MusikcubeData":{"type":"object","properties":{"url":{"type":"string","description":"URL of the Musikcube Websocket (Metadata) server to connect to\n\nYou MUST have enabled 'metadata' server and set a password: https://github.com/clangen/musikcube/wiki/remote-api-documentation\n * musikcube -> settings -> server setup\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `7905`","examples":["ws://localhost:7905"],"default":"ws://localhost:7905"},"password":{"type":"string","description":"Password set in Musikcube https://github.com/clangen/musikcube/wiki/remote-api-documentation\n\n* musikcube -> settings -> server setup -> password"},"device_id":{"type":"string"}},"required":["password"],"title":"MusikcubeData"},"MusicCastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusicCastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musiccast"]}},"required":["data","type"],"title":"MusicCastSourceAIOConfig"},"MusicCastData":{"type":"object","properties":{"url":{"type":"string","description":"The host or URL of the YamahaExtendedControl endpoint to use","examples":[["192.168.0.101","http://192.168.0.101/YamahaExtendedControl"]]}},"required":["url"],"title":"MusicCastData"},"MPDSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPDData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/MPDSourceOptions"},"type":{"type":"string","enum":["mpd"]}},"required":["data","options","type"],"title":"MPDSourceAIOConfig"},"MPDData":{"type":"object","properties":{"url":{"type":"string","description":"URL:PORT of the MPD server to connect to\n\nTo use this you must have TCP connections enabled for your MPD server https://mpd.readthedocs.io/en/stable/user.html#client-connections","examples":["localhost:6600"],"default":"localhost:6600"},"path":{"type":"string","description":"If using socket specify the path instead of url.\n\ntrailing `~` is replaced by your home directory"},"password":{"type":"string","description":"Password for the server, if set https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords"}},"title":"MPDData"},"MPDSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"MPDSourceOptions"},"VLCSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/VLCData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/VLCSourceOptions"},"type":{"type":"string","enum":["vlc"]}},"required":["data","type"],"title":"VLCSourceAIOConfig"},"VLCData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL:PORT of the VLC server to connect to\n\nTo use this you must have the Web (http) interface module enabled and a password set https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc","examples":["localhost:8080"],"default":"localhost:8080"},"password":{"type":"string","description":"Password for the server"}},"required":["password"],"title":"VLCData"},"VLCSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"filenamePatterns":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A list of regular expressions to use to extract metadata (title, album, artist) from a filename\n\nUsed when VLC reports only the filename for the current audio track"},"logFilenamePatterns":{"type":"boolean","description":"Log to DEBUG when a filename-only track is matched or not matched by filenamePatterns","default":false},"dumpVlcMetadata":{"type":"boolean","description":"Dump all the metadata VLC reports for an audio track to DEBUG.\n\nUse this if reporting an issue with VLC not correctly capturing metadata for a track.","default":false}},"title":"VLCSourceOptions"},"IcecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/IcecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/IcecastSourceOptions"},"type":{"type":"string","enum":["icecast"]}},"required":["data","type"],"title":"IcecastSourceAIOConfig"},"IcecastData":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/definitions/IcecastSource"}},"icestatsEndpoint":{"type":"string"},"statsEndpoint":{"type":"string"},"nextsongsEndpoint":{"type":"string"},"sevenhtmlEndpoint":{"type":"string"},"icyMetaInt":{"type":"number"},"url":{"type":"string","description":"The Icecast stream URL"}},"required":["url"],"title":"IcecastData"},"IcecastSource":{"type":"string","enum":["icy","ogg","icestats","stats","sevenhtml","nextsongs"],"title":"IcecastSource"},"IcecastSourceOptions":{"type":"object","properties":{"systemScrobble":{"type":"boolean","description":"For Sources that support manual listening, should MS default to scrobbling when no user interaction has occurred?\n\nIf not specified MS will use a Source's specific behavior, see Source's documentation."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"IcecastSourceOptions"},"AzuracastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/AzuracastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["azuracast"]}},"required":["data","type"],"title":"AzuracastSourceAIOConfig"},"AzuracastData":{"type":"object","properties":{"url":{"type":"string","description":"Base URL of the Azuracast instance\n\nThis does NOT include the station. If a station is included it will be ignored. Use `station` field to specify station, if necessary","examples":["https://radio.mydomain.tld","http://localhost:80"]},"station":{"type":"string","description":"The specific station to monitor\n\nScrobbling will only occur if any of the monitor conditions are met AND the station is ONLINE.\n\nTo monitor multiple stations create a Source for each station.","examples":["my-station-1"]},"monitorWhenListeners":{"type":["boolean","number"],"description":"Only activate scrobble monitoring if station\n\n* `true` => has any current listeners\n* `number` => has EQUAL TO or MORE THAN X number of listeners"},"monitorWhenLive":{"type":"boolean","description":"Only activate scrobble monitoring if station has a live DJ/Streamer","default":true},"apiKey":{"type":"string","description":"API Key used to access data about private streams\n\nhttps://www.azuracast.com/docs/developers/apis/#api-authentication"}},"required":["url","station"],"title":"AzuracastData"},"KoitoSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KoitoSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `koito.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["koito"]}},"required":["data","type"],"title":"KoitoSourceAIOConfig"},"KoitoSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoSourceData"},"TealSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/TealSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/TealSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"Should always be `souce` when using Tealfm as a Source","default":"source","examples":["source"]},"type":{"type":"string","enum":["tealfm"]}},"required":["data","type"],"title":"TealSourceAIOConfig"},"TealSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealSourceData"},"TealSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealSourceOptions"},"RockskySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/RockskySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/RockskySourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `rocksky.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["rocksky"]}},"required":["data","type"],"title":"RockskySourceAIOConfig"},"RockskySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockskySourceData"},"RockskySourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockskySourceOptions"}}} \ No newline at end of file +{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"sourceDefaults":{"$ref":"#/definitions/SourceRetryOptions"},"sources":{"type":"array","items":{"$ref":"#/definitions/SourceAIOConfig"}}},"definitions":{"SourceRetryOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]}},"title":"SourceRetryOptions"},"SourceAIOConfig":{"anyOf":[{"$ref":"#/definitions/SpotifySourceAIOConfig"},{"$ref":"#/definitions/PlexSourceAIOConfig"},{"$ref":"#/definitions/PlexApiSourceAIOConfig"},{"$ref":"#/definitions/TautulliSourceAIOConfig"},{"$ref":"#/definitions/DeezerSourceAIOConfig"},{"$ref":"#/definitions/DeezerInternalAIOConfig"},{"$ref":"#/definitions/ListenbrainzEndpointSourceAIOConfig"},{"$ref":"#/definitions/LastFMEndpointSourceAIOConfig"},{"$ref":"#/definitions/SubsonicSourceAIOConfig"},{"$ref":"#/definitions/JellySourceAIOConfig"},{"$ref":"#/definitions/JellyApiSourceAIOConfig"},{"$ref":"#/definitions/LastFmSouceAIOConfig"},{"$ref":"#/definitions/YTMusicSourceAIOConfig"},{"$ref":"#/definitions/MPRISSourceAIOConfig"},{"$ref":"#/definitions/MopidySourceAIOConfig"},{"$ref":"#/definitions/ListenBrainzSourceAIOConfig"},{"$ref":"#/definitions/JRiverSourceAIOConfig"},{"$ref":"#/definitions/KodiSourceAIOConfig"},{"$ref":"#/definitions/WebScrobblerSourceAIOConfig"},{"$ref":"#/definitions/ChromecastSourceAIOConfig"},{"$ref":"#/definitions/MalojaSourceAIOConfig"},{"$ref":"#/definitions/MusikcubeSourceAIOConfig"},{"$ref":"#/definitions/MusicCastSourceAIOConfig"},{"$ref":"#/definitions/MPDSourceAIOConfig"},{"$ref":"#/definitions/VLCSourceAIOConfig"},{"$ref":"#/definitions/IcecastSourceAIOConfig"},{"$ref":"#/definitions/AzuracastSourceAIOConfig"},{"$ref":"#/definitions/KoitoSourceAIOConfig"},{"$ref":"#/definitions/TealSourceAIOConfig"},{"$ref":"#/definitions/RockskySourceAIOConfig"}],"title":"SourceAIOConfig"},"SpotifySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SpotifySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["spotify"]}},"required":["data","type"],"title":"SpotifySourceAIOConfig"},"SpotifySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)\n\nIt is unlikely you should need to change this unless you scrobble many very short tracks often\n\nReading:\n* https://developer.spotify.com/documentation/web-api/guides/rate-limits/\n* https://medium.com/mendix/limiting-your-amount-of-calls-in-mendix-most-of-the-time-rest-835dde55b10e\n * Rate limit may ~180 req/min\n* https://community.spotify.com/t5/Spotify-for-Developers/Web-API-ratelimit/m-p/5503150/highlight/true#M7930\n * Informally indicated as 20 req/sec? Probably for burstiness","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"spotify client id","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"clientSecret":{"type":"string","description":"spotify client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"spotify redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/callback","examples":["http://localhost:9078/callback"]}},"required":["clientId","clientSecret"],"title":"SpotifySourceData"},"CommonSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonSourceOptions"},"LogLevel":{"type":"string","enum":["silent","fatal","error","warn","info","log","verbose","debug"],"description":"Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.","title":"LogLevel"},"FileLogOptions":{"type":"object","properties":{"timestamp":{"type":"string","enum":["unix","iso","auto"],"description":"For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime","default":"auto"},"size":{"type":["number","string"],"description":"The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.","default":"10MB"},"frequency":{"anyOf":[{"type":"string","enum":["daily"]},{"type":"string","enum":["hourly"]},{"type":"number"}],"description":"The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.","default":"daily"},"path":{"anyOf":[{"type":"string"},{"$comment":"() => string"}],"description":"The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD","default":"./logs/app.log"},"level":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false}],"description":"Specify the minimum log level to output to rotating files. If `false` no log files will be created."}},"title":"FileLogOptions"},"ScrobbleThresholds":{"type":"object","properties":{"duration":{"type":["number","null"],"description":"The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.","default":240,"examples":[240]},"percent":{"type":["number","null"],"description":"The percentage (as an integer) of a track that should have been seen played before it should be scrobbled. Only used if the Source provides information about how long the track is.\n\nSet to null to disable.\n\nNOTE: This should be used with care when the Source is a \"polling\" type (has an 'interval' property). If the track is short and the interval is too high MS may ignore the track if percentage is high because it had not \"seen\" the track for long enough from first discovery, even if you have been playing the track for longer.","default":50,"examples":[50]}},"title":"ScrobbleThresholds"},"PlayTransformOptions":{"type":"object","properties":{"log":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["all"]}]},"preCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"compare":{"type":"object","properties":{"candidate":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"existing":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}}},"postCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}},"title":"PlayTransformOptions"},"PlayTransformPartsConfig":{"anyOf":[{"$ref":"#/definitions/PlayTransformPartsArray%3CSearchAndReplaceTerm%3E"},{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"}],"title":"PlayTransformPartsConfig"},"PlayTransformPartsArray":{"type":"array","items":{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"},"title":"PlayTransformPartsArray"},"PlayTransformParts":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"title":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"artists":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"album":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}}},"title":"PlayTransformParts"},"WhenConditionsConfig":{"$ref":"#/definitions/WhenConditions%3Cstring%3E","title":"WhenConditionsConfig"},"WhenConditions":{"type":"array","items":{"$ref":"#/definitions/WhenParts%3Cstring%3E"},"title":"WhenConditions"},"WhenParts":{"$ref":"#/definitions/PlayTransformPartsAtomic%3Cstring%3E","title":"WhenParts"},"PlayTransformPartsAtomic":{"type":"object","properties":{"title":{"type":"string"},"artists":{"type":"string"},"album":{"type":"string"}},"title":"PlayTransformPartsAtomic"},"SearchAndReplaceTerm":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/ConditionalSearchAndReplaceTerm"}],"title":"SearchAndReplaceTerm"},"ConditionalSearchAndReplaceTerm":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"search":{},"replace":{}},"required":["search","replace"],"title":"ConditionalSearchAndReplaceTerm"},"PlexSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","type"],"title":"PlexSourceAIOConfig"},"PlexSourceData":{"type":"object","properties":{"user":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"libraries":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of libraries to scrobble tracks from\n\nIf none are provided tracks from all libraries will be scrobbled","examples":[["Audio","Music"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName"]]}},"title":"PlexSourceData"},"PlexApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/PlexApiOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","options","type"],"title":"PlexApiSourceAIOConfig"},"PlexApiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"token":{"type":"string"},"url":{"type":"string","description":"http(s)://HOST:PORT of the Plex server to connect to"},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"}},"required":["url"],"title":"PlexApiData"},"PlexApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"ignoreInvalidCert":{"type":"boolean","description":"Ignore invalid cert errors when connecting to Plex\n\nUseful for Plex servers using \"Required\" Secure Connections with self-signed certificates\n\nDo not enable unless you know you need this.","default":false}},"title":"PlexApiOptions"},"TautulliSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["tautulli"]}},"required":["data","type"],"title":"TautulliSourceAIOConfig"},"DeezerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerSourceAIOConfig"},"DeezerData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"deezer client id","examples":["a89cba1569901a0671d5a9875fed4be1"]},"clientSecret":{"type":"string","description":"deezer client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/deezer/callback","examples":["http://localhost:9078/deezer/callback"]}},"required":["clientId","clientSecret"],"title":"DeezerData"},"DeezerInternalAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerInternalData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/DeezerInternalSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerInternalAIOConfig"},"DeezerInternalData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"arl":{"type":"string","description":"ARL retrieved from Deezer response header"},"userAgent":{"type":"string","description":"User agent","default":"Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0"}},"required":["arl"],"title":"DeezerInternalData"},"DeezerInternalSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"fuzzyDiscoveryIgnore":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["aggressive"]}]}},"title":"DeezerInternalSourceOptions"},"ListenbrainzEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenbrainzEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlz"]}},"required":["type"],"title":"ListenbrainzEndpointSourceAIOConfig"},"ListenbrainzEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Listenbrainz endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/listenbrainz/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/listenbrainz/originb\n\nIf no slug is found from an extension's incoming webhook event the first Listenbrainz source without a slug will be used"},"token":{"type":["string","null"],"description":"If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source\n\nSee: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#add-the-user-token-to-your-requests"}},"title":"ListenbrainzEndpointData"},"LastFMEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFMEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlfm"]}},"required":["type"],"title":"LastFMEndpointSourceAIOConfig"},"LastFMEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Last.fm endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/lastfm/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/lastfm/originb\n\nIf no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used"}},"title":"LastFMEndpointData"},"SubsonicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SubsonicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["subsonic"]}},"required":["data","type"],"title":"SubsonicSourceAIOConfig"},"SubsonicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the subsonic media server to query","examples":["http://airsonic.local"]},"user":{"type":"string","description":"Username to login to the server with","examples":[["MyUser"]]},"password":{"type":"string","description":"Password for the user to login to the server with","examples":["MyPassword"]},"ignoreTlsErrors":{"type":"boolean","description":"If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection\n\nWARNING: This should be used with caution as your traffic may not be encrypted.","default":false},"legacyAuthentication":{"type":"boolean","description":"Older Subsonic versions, and some badly implemented servers (Nextcloud), use legacy authentication which sends your password in CLEAR TEXT. This is less secure than the newer, recommended hashing authentication method but in some cases it is needed. See \"Authentication\" section here => https://www.subsonic.org/pages/api.jsp\n\nIf this option is not specified it will be turned on if the subsonic server responds with error code 41 \"Token authentication not supported for LDAP users.\" -- See Error Handling section => https://www.subsonic.org/pages/api.jsp","default":false},"usersAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf undefined or an empty string/list MS will scrobble activity from all users"}},"required":["url","user","password"],"title":"SubsonicData"},"JellySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","type"],"title":"JellySourceAIOConfig"},"JellyData":{"type":"object","properties":{"users":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName1"]]}},"title":"JellyData"},"JellyApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/JellyApiOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","options","type"],"title":"JellyApiSourceAIOConfig"},"JellyApiData":{"type":"object","properties":{"url":{"type":"string","description":"HOST:PORT of the Jellyfin server to connect to"},"user":{"type":"string","description":"The username of the user to authenticate for or track scrobbles for"},"password":{"type":"string","description":"Password of the username to authenticate for\n\nRequired if `apiKey` is not provided."},"apiKey":{"type":"string","description":"API Key to authenticate with.\n\nRequired if `password` is not provided."},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"},"additionalAllowedLibraryTypes":{"type":"array","items":{},"description":"Allow MS to scrobble audio media in libraries classified other than 'music'\n\n`librariesAllow` will achieve the same result as this but this is more convenient if you do not want to explicitly list every library name or are only using `librariesBlock`"},"allowUnknown":{"type":"boolean","description":"Force media with a type of \"Unknown\" to be counted as Audio","default":false},"frontendUrlOverride":{"type":"string","description":"HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from)\nIf unspecified it will use the normal server HOST and PORT from the `url`\nNecessary if you are using a reverse proxy or other network configuration that prevents the frontend from accessing the server directly\n\nENV: JELLYFIN_FRONTEND_URL_OVERRIDE"}},"required":["url","user"],"title":"JellyApiData"},"JellyApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"JellyApiOptions"},"LastFmSouceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFmSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["lastfm"]}},"required":["data","type"],"title":"LastFmSouceAIOConfig"},"LastFmSourceData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]},"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"}},"required":["apiKey","secret"],"title":"LastFmSourceData"},"YTMusicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/YTMusicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"type":"object","properties":{"logAuth":{"type":"boolean","description":"When true MS will log to DEBUG all of the credentials data it receives from YTM"},"logDiff":{"type":"boolean","description":"Always log history diff\n\nBy default MS will log to `WARN` if history diff is inconsistent but does not log if diff is expected (on new tracks found)\nSet this to `true` to ALWAYS log diff on new tracks. Expected diffs will log to `DEBUG` and inconsistent diffs will continue to log to `WARN`","default":false},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}}},"type":{"type":"string","enum":["ytmusic"]}},"required":["type"],"title":"YTMusicSourceAIOConfig"},"YTMusicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"cookie":{"type":"string","description":"The cookie retrieved from the Request Headers of music.youtube.com after logging in.\n\nSee https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers for how to retrieve this value.","examples":["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]},"clientId":{"type":"string","description":"Google Cloud Console project OAuth Client ID\n\nGenerated from a custom OAuth Client, see docs"},"clientSecret":{"type":"string","description":"Google Cloud Console project OAuth Client Secret\n\nGenerated from a custom OAuth Client, see docs"},"redirectUri":{"type":"string","description":"Google Cloud Console project OAuth Client Authorized redirect URI\n\nGenerated from a custom OAuth Client, see docs. multi-scrobbler will generate a default based on BASE_URL.\nOnly specify this if the default does not work for you."},"innertubeOptions":{"$ref":"#/definitions/InnertubeOptions","description":"Additional options for authorization and tailoring YTM client"}},"title":"YTMusicData"},"InnertubeOptions":{"type":"object","properties":{"po_token":{"type":"string","description":"Proof of Origin token\n\nMay be required if YTM starts returning 403"},"visitor_data":{"type":"string","description":"Visitor ID value found in VISITOR_INFO1_LIVE or visitorData cookie\n\nMay be required if YTM starts returning 403"},"account_index":{"type":"number","description":"If account login results in being able to choose multiple account, use a zero-based index to choose which one to monitor","examples":[0,1]},"location":{"type":"string"},"lang":{"type":"string"},"generate_session_locally":{"type":"boolean"},"device_category":{"type":"string"},"client_type":{"type":"string"},"timezone":{"type":"string"}},"title":"InnertubeOptions"},"MPRISSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPRISData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mpris"]}},"required":["data","type"],"title":"MPRISSourceAIOConfig"},"MPRISData":{"type":"object","properties":{"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any players that START WITH these values, case-insensitive","examples":[["spotify","vlc"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","vlc"]]}},"title":"MPRISData"},"MopidySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MopidyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mopidy"]}},"required":["data","type"],"title":"MopidySourceAIOConfig"},"MopidyData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Mopidy HTTP server to connect to\n\nYou MUST have Mopidy-HTTP extension enabled: https://mopidy.com/ext/http\n\nmulti-scrobbler connects to the WebSocket endpoint that ultimately looks like this => `ws://localhost:6680/mopidy/ws/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `6680`\n* Path => `/mopidy/ws/`","examples":["ws://localhost:6680/mopidy/ws/"],"default":"ws://localhost:6680/mopidy/ws/"},"uriBlacklist":{"type":"array","items":{"type":"string"},"description":"Do not scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Don't scrobble tracks from soundcloud by adding 'soundcloud' to this list.\n\nList is ignored if uriWhitelist is used."},"uriWhitelist":{"type":"array","items":{"type":"string"},"description":"Only scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Only scrobble tracks from soundcloud by adding 'soundcloud' to this list."},"albumBlacklist":{"type":"array","items":{"type":"string"},"description":"Remove album data that matches any case-insensitive string from this list when scrobbling,\n\nFor certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use \"Soundcloud\" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake name to this list. Multi-scrobbler will still scrobble the track, just without the bad data.","examples":[["Soundcloud","Mixcloud"]],"default":["Soundcloud"]}},"title":"MopidyData"},"ListenBrainzSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenBrainzSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `listenbrainz.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["listenbrainz"]}},"required":["data","type"],"title":"ListenBrainzSourceAIOConfig"},"ListenBrainzSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzSourceData"},"JRiverSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JRiverData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jriver"]}},"required":["data","type"],"title":"JRiverSourceAIOConfig"},"JRiverData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the JRiver HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:52199/MCWS/v1/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `52199`\n* Path => `/MCWS/v1/`","examples":["http://localhost:52199/MCWS/v1/"],"default":"http://localhost:52199/MCWS/v1/"},"username":{"type":"string","description":"If you have enabled authentication, the username you set"},"password":{"type":"string","description":"If you have enabled authentication, the password you set"}},"required":["url"],"title":"JRiverData"},"KodiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KodiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["kodi"]}},"required":["data","type"],"title":"KodiSourceAIOConfig"},"KodiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Kodi HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:8080/jsonrpc`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `8080`\n* Path => `/jsonrpc`","examples":["http://localhost:8080/jsonrpc"],"default":"http://localhost:8080/jsonrpc"},"username":{"type":"string","description":"The username set for Remote Control via Web Sever"},"password":{"type":"string","description":"The password set for Remote Control via Web Sever"}},"required":["url","username","password"],"title":"KodiData"},"WebScrobblerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/WebScrobblerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["webscrobbler"]}},"required":["type"],"title":"WebScrobblerSourceAIOConfig"},"WebScrobblerData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIn WebScrobbler's Webhook you must set an 'API URL'. All MS WebScrobbler sources must start like:\n\nhttp://localhost:9078/api/webscrobbler\n\nIf you are using multiple WebScrobbler sources (scrobbles for many users) you must use a slug to match Sources with each users extension.\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/webscrobbler/usera\n* slug: 'userb' => API URL: http://localhost:9078/api/webscrobbler/userb\n\nIf no slug is found from an extension's incoming webhook event the first WebScrobbler source without a slug will be used"},"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Block scrobbling from specific WebScrobbler Connectors","examples":[["youtube"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only allow scrobbling from specific WebScrobbler Connectors","examples":[["mixcloud","soundcloud","bandcamp"]]}},"title":"WebScrobblerData"},"ChromecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ChromecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["chromecast"]}},"required":["data","type"],"title":"ChromecastSourceAIOConfig"},"ChromecastData":{"type":"object","properties":{"blacklistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"whitelistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any cast device that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"blacklistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any application that START WITH these values, case-insensitive","examples":[["spotify","pandora"]]},"whitelistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any application that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","pandora"]]},"useAvahi":{"type":"boolean","description":"Try to use Avahi and avahi-browse to resolve mDNS devices instead of native mDNS querying\n\nUseful for docker (alpine) container where mDNS resolution is not yet supported. Avahi socket must be exposed to the container and avahi-tools must be installed.","default":false},"useAutoDiscovery":{"type":"boolean","description":"Use mDNS to discovery Google Cast devices on your next automatically?\n\nIf not explicitly set then it is TRUE if `devices` is not set"},"devices":{"type":"array","items":{"$ref":"#/definitions/ChromecastDeviceInfo"},"description":"A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set"},"allowUnknownMedia":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Chromecast Apps report a \"media type\" in the status info returned for whatever is currently playing\n\n* If set to TRUE then Music AND Generic/Unknown media will be tracked for ALL APPS\n* If set to FALSE then only media explicitly typed as Music will be tracked for ALL APPS\n* If set to a list then only Apps whose name contain one of these values, case-insensitive, will have Music AND Generic/Unknown tracked\n\nSee https://developers.google.com/cast/docs/media/messages#MediaInformation \"metadata\" property","default":false},"forceMediaRecognitionOn":{"type":"array","items":{"type":"string"},"description":"Media provided by any App whose name is listed here will ALWAYS be tracked, regardless of the \"media type\" reported\n\nApps will be recognized if they CONTAIN any of these values, case-insensitive"}},"title":"ChromecastData"},"ChromecastDeviceInfo":{"type":"object","properties":{"name":{"type":"string","description":"A friendly name to identify this device","examples":["MySmartTV"]},"address":{"type":"string","description":"The IP address of the device","examples":["192.168.0.115"]}},"required":["name","address"],"title":"ChromecastDeviceInfo"},"MalojaSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MalojaSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `maloja.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["maloja"]}},"required":["data","type"],"title":"MalojaSourceAIOConfig"},"MalojaSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaSourceData"},"MusikcubeSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusikcubeData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musikcube"]}},"required":["data","type"],"title":"MusikcubeSourceAIOConfig"},"MusikcubeData":{"type":"object","properties":{"url":{"type":"string","description":"URL of the Musikcube Websocket (Metadata) server to connect to\n\nYou MUST have enabled 'metadata' server and set a password: https://github.com/clangen/musikcube/wiki/remote-api-documentation\n * musikcube -> settings -> server setup\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `7905`","examples":["ws://localhost:7905"],"default":"ws://localhost:7905"},"password":{"type":"string","description":"Password set in Musikcube https://github.com/clangen/musikcube/wiki/remote-api-documentation\n\n* musikcube -> settings -> server setup -> password"},"device_id":{"type":"string"}},"required":["password"],"title":"MusikcubeData"},"MusicCastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusicCastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musiccast"]}},"required":["data","type"],"title":"MusicCastSourceAIOConfig"},"MusicCastData":{"type":"object","properties":{"url":{"type":"string","description":"The host or URL of the YamahaExtendedControl endpoint to use","examples":[["192.168.0.101","http://192.168.0.101/YamahaExtendedControl"]]}},"required":["url"],"title":"MusicCastData"},"MPDSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPDData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/MPDSourceOptions"},"type":{"type":"string","enum":["mpd"]}},"required":["data","options","type"],"title":"MPDSourceAIOConfig"},"MPDData":{"type":"object","properties":{"url":{"type":"string","description":"URL:PORT of the MPD server to connect to\n\nTo use this you must have TCP connections enabled for your MPD server https://mpd.readthedocs.io/en/stable/user.html#client-connections","examples":["localhost:6600"],"default":"localhost:6600"},"path":{"type":"string","description":"If using socket specify the path instead of url.\n\ntrailing `~` is replaced by your home directory"},"password":{"type":"string","description":"Password for the server, if set https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords"}},"title":"MPDData"},"MPDSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"MPDSourceOptions"},"VLCSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/VLCData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/VLCSourceOptions"},"type":{"type":"string","enum":["vlc"]}},"required":["data","type"],"title":"VLCSourceAIOConfig"},"VLCData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL:PORT of the VLC server to connect to\n\nTo use this you must have the Web (http) interface module enabled and a password set https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc","examples":["localhost:8080"],"default":"localhost:8080"},"password":{"type":"string","description":"Password for the server"}},"required":["password"],"title":"VLCData"},"VLCSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"filenamePatterns":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A list of regular expressions to use to extract metadata (title, album, artist) from a filename\n\nUsed when VLC reports only the filename for the current audio track"},"logFilenamePatterns":{"type":"boolean","description":"Log to DEBUG when a filename-only track is matched or not matched by filenamePatterns","default":false},"dumpVlcMetadata":{"type":"boolean","description":"Dump all the metadata VLC reports for an audio track to DEBUG.\n\nUse this if reporting an issue with VLC not correctly capturing metadata for a track.","default":false}},"title":"VLCSourceOptions"},"IcecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/IcecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/IcecastSourceOptions"},"type":{"type":"string","enum":["icecast"]}},"required":["data","type"],"title":"IcecastSourceAIOConfig"},"IcecastData":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/definitions/IcecastSource"}},"icestatsEndpoint":{"type":"string"},"statsEndpoint":{"type":"string"},"nextsongsEndpoint":{"type":"string"},"sevenhtmlEndpoint":{"type":"string"},"icyMetaInt":{"type":"number"},"url":{"type":"string","description":"The Icecast stream URL"}},"required":["url"],"title":"IcecastData"},"IcecastSource":{"type":"string","enum":["icy","ogg","icestats","stats","sevenhtml","nextsongs"],"title":"IcecastSource"},"IcecastSourceOptions":{"type":"object","properties":{"systemScrobble":{"type":"boolean","description":"For Sources that support manual listening, should MS default to scrobbling when no user interaction has occurred?\n\nIf not specified MS will use a Source's specific behavior, see Source's documentation."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"IcecastSourceOptions"},"AzuracastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/AzuracastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["azuracast"]}},"required":["data","type"],"title":"AzuracastSourceAIOConfig"},"AzuracastData":{"type":"object","properties":{"url":{"type":"string","description":"Base URL of the Azuracast instance\n\nThis does NOT include the station. If a station is included it will be ignored. Use `station` field to specify station, if necessary","examples":["https://radio.mydomain.tld","http://localhost:80"]},"station":{"type":"string","description":"The specific station to monitor\n\nScrobbling will only occur if any of the monitor conditions are met AND the station is ONLINE.\n\nTo monitor multiple stations create a Source for each station.","examples":["my-station-1"]},"monitorWhenListeners":{"type":["boolean","number"],"description":"Only activate scrobble monitoring if station\n\n* `true` => has any current listeners\n* `number` => has EQUAL TO or MORE THAN X number of listeners"},"monitorWhenLive":{"type":"boolean","description":"Only activate scrobble monitoring if station has a live DJ/Streamer","default":true},"apiKey":{"type":"string","description":"API Key used to access data about private streams\n\nhttps://www.azuracast.com/docs/developers/apis/#api-authentication"}},"required":["url","station"],"title":"AzuracastData"},"KoitoSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KoitoSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `koito.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["koito"]}},"required":["data","type"],"title":"KoitoSourceAIOConfig"},"KoitoSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoSourceData"},"TealSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/TealSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/TealSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"Should always be `souce` when using Tealfm as a Source","default":"source","examples":["source"]},"type":{"type":"string","enum":["tealfm"]}},"required":["data","type"],"title":"TealSourceAIOConfig"},"TealSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealSourceData"},"TealSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealSourceOptions"},"RockskySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/RockskySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/RockskySourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `rocksky.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["rocksky"]}},"required":["data","type"],"title":"RockskySourceAIOConfig"},"RockskySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockskySourceData"},"RockskySourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockskySourceOptions"}}} diff --git a/src/backend/common/schema/aio.json b/src/backend/common/schema/aio.json index 36b2296b..4e75f1de 100644 --- a/src/backend/common/schema/aio.json +++ b/src/backend/common/schema/aio.json @@ -1 +1 @@ -{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"sourceDefaults":{"$ref":"#/definitions/SourceDefaults"},"clientDefaults":{"$ref":"#/definitions/ClientDefaults"},"sources":{"type":"array","items":{"$ref":"#/definitions/SourceAIOConfig"}},"clients":{"type":"array","items":{"$ref":"#/definitions/ClientAIOConfig"}},"webhooks":{"type":"array","items":{"$ref":"#/definitions/WebhookConfig"}},"port":{"type":"number","description":"Set the port the multi-scrobbler UI will be served from","default":9078,"examples":[9078]},"baseUrl":{"type":"string","description":"Set the Base URL the application should assume the UI is served from.\n\nThis will affect how default redirect URLs are generated (spotify, lastfm, deezer) and some logging messages.\n\nIt will NOT set the actual interface/IP that the application is listening on.\n\nThis can also be set using the BASE_URL environmental variable.","default":"http://localhost","examples":["http://localhost","http://192.168.0.101","https://ms.myDomain.tld"]},"logging":{"$ref":"#/definitions/LogOptions"},"disableWeb":{"type":"boolean","description":"Disable web server from running/listening on port.\n\nThis will also make any ingress sources (Plex, Jellyfin, Tautulli, etc...) unusable"},"debugMode":{"type":"boolean","description":"Enables ALL relevant logging and debug options for all sources/clients, when none are defined.\n\nThis is a convenience shortcut for enabling all output needed to troubleshoot an issue and does not need to be on for normal operation.\n\nIt can also be enabled with the environmental variable DEBUG_MODE=true","default":false,"examples":[false]},"cache":{"$ref":"#/definitions/CacheConfigOptions"}},"definitions":{"SourceDefaults":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"SourceDefaults"},"LogLevel":{"type":"string","enum":["silent","fatal","error","warn","info","log","verbose","debug"],"description":"Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.","title":"LogLevel"},"FileLogOptions":{"type":"object","properties":{"timestamp":{"type":"string","enum":["unix","iso","auto"],"description":"For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime","default":"auto"},"size":{"type":["number","string"],"description":"The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.","default":"10MB"},"frequency":{"anyOf":[{"type":"string","enum":["daily"]},{"type":"string","enum":["hourly"]},{"type":"number"}],"description":"The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.","default":"daily"},"path":{"anyOf":[{"type":"string"},{"$comment":"() => string"}],"description":"The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD","default":"./logs/app.log"},"level":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false}],"description":"Specify the minimum log level to output to rotating files. If `false` no log files will be created."}},"title":"FileLogOptions"},"ScrobbleThresholds":{"type":"object","properties":{"duration":{"type":["number","null"],"description":"The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.","default":240,"examples":[240]},"percent":{"type":["number","null"],"description":"The percentage (as an integer) of a track that should have been seen played before it should be scrobbled. Only used if the Source provides information about how long the track is.\n\nSet to null to disable.\n\nNOTE: This should be used with care when the Source is a \"polling\" type (has an 'interval' property). If the track is short and the interval is too high MS may ignore the track if percentage is high because it had not \"seen\" the track for long enough from first discovery, even if you have been playing the track for longer.","default":50,"examples":[50]}},"title":"ScrobbleThresholds"},"PlayTransformOptions":{"type":"object","properties":{"log":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["all"]}]},"preCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"compare":{"type":"object","properties":{"candidate":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"existing":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}}},"postCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}},"title":"PlayTransformOptions"},"PlayTransformPartsConfig":{"anyOf":[{"$ref":"#/definitions/PlayTransformPartsArray%3CSearchAndReplaceTerm%3E"},{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"}],"title":"PlayTransformPartsConfig"},"PlayTransformPartsArray":{"type":"array","items":{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"},"title":"PlayTransformPartsArray"},"PlayTransformParts":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"title":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"artists":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"album":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}}},"title":"PlayTransformParts"},"WhenConditionsConfig":{"$ref":"#/definitions/WhenConditions%3Cstring%3E","title":"WhenConditionsConfig"},"WhenConditions":{"type":"array","items":{"$ref":"#/definitions/WhenParts%3Cstring%3E"},"title":"WhenConditions"},"WhenParts":{"$ref":"#/definitions/PlayTransformPartsAtomic%3Cstring%3E","title":"WhenParts"},"PlayTransformPartsAtomic":{"type":"object","properties":{"title":{"type":"string"},"artists":{"type":"string"},"album":{"type":"string"}},"title":"PlayTransformPartsAtomic"},"SearchAndReplaceTerm":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/ConditionalSearchAndReplaceTerm"}],"title":"SearchAndReplaceTerm"},"ConditionalSearchAndReplaceTerm":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"search":{},"replace":{}},"required":["search","replace"],"title":"ConditionalSearchAndReplaceTerm"},"ClientDefaults":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"ClientDefaults"},"MatchLoggingOptions":{"type":"object","properties":{"onNoMatch":{"type":"boolean","description":"Log to DEBUG when a new track does NOT match an existing scrobble","default":false,"examples":[false]},"onMatch":{"type":"boolean","description":"Log to DEBUG when a new track DOES match an existing scrobble","default":false,"examples":[false]},"confidenceBreakdown":{"type":"boolean","description":"Include confidence breakdowns in track match logging, if applicable","default":false,"examples":[false]}},"description":"Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.","title":"MatchLoggingOptions"},"SourceAIOConfig":{"anyOf":[{"$ref":"#/definitions/SpotifySourceAIOConfig"},{"$ref":"#/definitions/PlexSourceAIOConfig"},{"$ref":"#/definitions/PlexApiSourceAIOConfig"},{"$ref":"#/definitions/TautulliSourceAIOConfig"},{"$ref":"#/definitions/DeezerSourceAIOConfig"},{"$ref":"#/definitions/DeezerInternalAIOConfig"},{"$ref":"#/definitions/ListenbrainzEndpointSourceAIOConfig"},{"$ref":"#/definitions/LastFMEndpointSourceAIOConfig"},{"$ref":"#/definitions/SubsonicSourceAIOConfig"},{"$ref":"#/definitions/JellySourceAIOConfig"},{"$ref":"#/definitions/JellyApiSourceAIOConfig"},{"$ref":"#/definitions/LastFmSouceAIOConfig"},{"$ref":"#/definitions/YTMusicSourceAIOConfig"},{"$ref":"#/definitions/MPRISSourceAIOConfig"},{"$ref":"#/definitions/MopidySourceAIOConfig"},{"$ref":"#/definitions/ListenBrainzSourceAIOConfig"},{"$ref":"#/definitions/JRiverSourceAIOConfig"},{"$ref":"#/definitions/KodiSourceAIOConfig"},{"$ref":"#/definitions/WebScrobblerSourceAIOConfig"},{"$ref":"#/definitions/ChromecastSourceAIOConfig"},{"$ref":"#/definitions/MalojaSourceAIOConfig"},{"$ref":"#/definitions/MusikcubeSourceAIOConfig"},{"$ref":"#/definitions/MusicCastSourceAIOConfig"},{"$ref":"#/definitions/MPDSourceAIOConfig"},{"$ref":"#/definitions/VLCSourceAIOConfig"},{"$ref":"#/definitions/IcecastSourceAIOConfig"},{"$ref":"#/definitions/AzuracastSourceAIOConfig"},{"$ref":"#/definitions/KoitoSourceAIOConfig"},{"$ref":"#/definitions/TealSourceAIOConfig"},{"$ref":"#/definitions/RockskySourceAIOConfig"}],"title":"SourceAIOConfig"},"SpotifySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SpotifySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["spotify"]}},"required":["data","type"],"title":"SpotifySourceAIOConfig"},"SpotifySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)\n\nIt is unlikely you should need to change this unless you scrobble many very short tracks often\n\nReading:\n* https://developer.spotify.com/documentation/web-api/guides/rate-limits/\n* https://medium.com/mendix/limiting-your-amount-of-calls-in-mendix-most-of-the-time-rest-835dde55b10e\n * Rate limit may ~180 req/min\n* https://community.spotify.com/t5/Spotify-for-Developers/Web-API-ratelimit/m-p/5503150/highlight/true#M7930\n * Informally indicated as 20 req/sec? Probably for burstiness","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"spotify client id","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"clientSecret":{"type":"string","description":"spotify client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"spotify redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/callback","examples":["http://localhost:9078/callback"]}},"required":["clientId","clientSecret"],"title":"SpotifySourceData"},"CommonSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonSourceOptions"},"PlexSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","type"],"title":"PlexSourceAIOConfig"},"PlexSourceData":{"type":"object","properties":{"user":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"libraries":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of libraries to scrobble tracks from\n\nIf none are provided tracks from all libraries will be scrobbled","examples":[["Audio","Music"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName"]]}},"title":"PlexSourceData"},"PlexApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/PlexApiOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","options","type"],"title":"PlexApiSourceAIOConfig"},"PlexApiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"token":{"type":"string"},"url":{"type":"string","description":"http(s)://HOST:PORT of the Plex server to connect to"},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"}},"required":["url"],"title":"PlexApiData"},"PlexApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"ignoreInvalidCert":{"type":"boolean","description":"Ignore invalid cert errors when connecting to Plex\n\nUseful for Plex servers using \"Required\" Secure Connections with self-signed certificates\n\nDo not enable unless you know you need this.","default":false}},"title":"PlexApiOptions"},"TautulliSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["tautulli"]}},"required":["data","type"],"title":"TautulliSourceAIOConfig"},"DeezerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerSourceAIOConfig"},"DeezerData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"deezer client id","examples":["a89cba1569901a0671d5a9875fed4be1"]},"clientSecret":{"type":"string","description":"deezer client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/deezer/callback","examples":["http://localhost:9078/deezer/callback"]}},"required":["clientId","clientSecret"],"title":"DeezerData"},"DeezerInternalAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerInternalData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/DeezerInternalSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerInternalAIOConfig"},"DeezerInternalData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"arl":{"type":"string","description":"ARL retrieved from Deezer response header"},"userAgent":{"type":"string","description":"User agent","default":"Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0"}},"required":["arl"],"title":"DeezerInternalData"},"DeezerInternalSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"fuzzyDiscoveryIgnore":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["aggressive"]}]}},"title":"DeezerInternalSourceOptions"},"ListenbrainzEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenbrainzEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlz"]}},"required":["type"],"title":"ListenbrainzEndpointSourceAIOConfig"},"ListenbrainzEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Listenbrainz endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/listenbrainz/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/listenbrainz/originb\n\nIf no slug is found from an extension's incoming webhook event the first Listenbrainz source without a slug will be used"},"token":{"type":["string","null"],"description":"If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source\n\nSee: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#add-the-user-token-to-your-requests"}},"title":"ListenbrainzEndpointData"},"LastFMEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFMEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlfm"]}},"required":["type"],"title":"LastFMEndpointSourceAIOConfig"},"LastFMEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Last.fm endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/lastfm/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/lastfm/originb\n\nIf no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used"}},"title":"LastFMEndpointData"},"SubsonicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SubsonicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["subsonic"]}},"required":["data","type"],"title":"SubsonicSourceAIOConfig"},"SubsonicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the subsonic media server to query","examples":["http://airsonic.local"]},"user":{"type":"string","description":"Username to login to the server with","examples":[["MyUser"]]},"password":{"type":"string","description":"Password for the user to login to the server with","examples":["MyPassword"]},"ignoreTlsErrors":{"type":"boolean","description":"If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection\n\nWARNING: This should be used with caution as your traffic may not be encrypted.","default":false},"legacyAuthentication":{"type":"boolean","description":"Older Subsonic versions, and some badly implemented servers (Nextcloud), use legacy authentication which sends your password in CLEAR TEXT. This is less secure than the newer, recommended hashing authentication method but in some cases it is needed. See \"Authentication\" section here => https://www.subsonic.org/pages/api.jsp\n\nIf this option is not specified it will be turned on if the subsonic server responds with error code 41 \"Token authentication not supported for LDAP users.\" -- See Error Handling section => https://www.subsonic.org/pages/api.jsp","default":false},"usersAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf undefined or an empty string/list MS will scrobble activity from all users"}},"required":["url","user","password"],"title":"SubsonicData"},"JellySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","type"],"title":"JellySourceAIOConfig"},"JellyData":{"type":"object","properties":{"users":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName1"]]}},"title":"JellyData"},"JellyApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/JellyApiOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","options","type"],"title":"JellyApiSourceAIOConfig"},"JellyApiData":{"type":"object","properties":{"url":{"type":"string","description":"HOST:PORT of the Jellyfin server to connect to"},"user":{"type":"string","description":"The username of the user to authenticate for or track scrobbles for"},"password":{"type":"string","description":"Password of the username to authenticate for\n\nRequired if `apiKey` is not provided."},"apiKey":{"type":"string","description":"API Key to authenticate with.\n\nRequired if `password` is not provided."},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"},"additionalAllowedLibraryTypes":{"type":"array","items":{},"description":"Allow MS to scrobble audio media in libraries classified other than 'music'\n\n`librariesAllow` will achieve the same result as this but this is more convenient if you do not want to explicitly list every library name or are only using `librariesBlock`"},"allowUnknown":{"type":"boolean","description":"Force media with a type of \"Unknown\" to be counted as Audio","default":false},"frontendUrlOverride":{"type":"string","description":"HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from)\nIf unspecified it will use the normal server HOST and PORT from the `url`\nNecessary if you are using a reverse proxy or other network configuration that prevents the frontend from accessing the server directly\n\nENV: JELLYFIN_FRONTEND_URL_OVERRIDE"}},"required":["url","user"],"title":"JellyApiData"},"JellyApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"JellyApiOptions"},"LastFmSouceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFmSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["lastfm"]}},"required":["data","type"],"title":"LastFmSouceAIOConfig"},"LastFmSourceData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]},"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"}},"required":["apiKey","secret"],"title":"LastFmSourceData"},"YTMusicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/YTMusicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"type":"object","properties":{"logAuth":{"type":"boolean","description":"When true MS will log to DEBUG all of the credentials data it receives from YTM"},"logDiff":{"type":"boolean","description":"Always log history diff\n\nBy default MS will log to `WARN` if history diff is inconsistent but does not log if diff is expected (on new tracks found)\nSet this to `true` to ALWAYS log diff on new tracks. Expected diffs will log to `DEBUG` and inconsistent diffs will continue to log to `WARN`","default":false},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}}},"type":{"type":"string","enum":["ytmusic"]}},"required":["type"],"title":"YTMusicSourceAIOConfig"},"YTMusicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"cookie":{"type":"string","description":"The cookie retrieved from the Request Headers of music.youtube.com after logging in.\n\nSee https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers for how to retrieve this value.","examples":["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]},"clientId":{"type":"string","description":"Google Cloud Console project OAuth Client ID\n\nGenerated from a custom OAuth Client, see docs"},"clientSecret":{"type":"string","description":"Google Cloud Console project OAuth Client Secret\n\nGenerated from a custom OAuth Client, see docs"},"redirectUri":{"type":"string","description":"Google Cloud Console project OAuth Client Authorized redirect URI\n\nGenerated from a custom OAuth Client, see docs. multi-scrobbler will generate a default based on BASE_URL.\nOnly specify this if the default does not work for you."},"innertubeOptions":{"$ref":"#/definitions/InnertubeOptions","description":"Additional options for authorization and tailoring YTM client"}},"title":"YTMusicData"},"InnertubeOptions":{"type":"object","properties":{"po_token":{"type":"string","description":"Proof of Origin token\n\nMay be required if YTM starts returning 403"},"visitor_data":{"type":"string","description":"Visitor ID value found in VISITOR_INFO1_LIVE or visitorData cookie\n\nMay be required if YTM starts returning 403"},"account_index":{"type":"number","description":"If account login results in being able to choose multiple account, use a zero-based index to choose which one to monitor","examples":[0,1]},"location":{"type":"string"},"lang":{"type":"string"},"generate_session_locally":{"type":"boolean"},"device_category":{"type":"string"},"client_type":{"type":"string"},"timezone":{"type":"string"}},"title":"InnertubeOptions"},"MPRISSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPRISData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mpris"]}},"required":["data","type"],"title":"MPRISSourceAIOConfig"},"MPRISData":{"type":"object","properties":{"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any players that START WITH these values, case-insensitive","examples":[["spotify","vlc"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","vlc"]]}},"title":"MPRISData"},"MopidySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MopidyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mopidy"]}},"required":["data","type"],"title":"MopidySourceAIOConfig"},"MopidyData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Mopidy HTTP server to connect to\n\nYou MUST have Mopidy-HTTP extension enabled: https://mopidy.com/ext/http\n\nmulti-scrobbler connects to the WebSocket endpoint that ultimately looks like this => `ws://localhost:6680/mopidy/ws/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `6680`\n* Path => `/mopidy/ws/`","examples":["ws://localhost:6680/mopidy/ws/"],"default":"ws://localhost:6680/mopidy/ws/"},"uriBlacklist":{"type":"array","items":{"type":"string"},"description":"Do not scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Don't scrobble tracks from soundcloud by adding 'soundcloud' to this list.\n\nList is ignored if uriWhitelist is used."},"uriWhitelist":{"type":"array","items":{"type":"string"},"description":"Only scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Only scrobble tracks from soundcloud by adding 'soundcloud' to this list."},"albumBlacklist":{"type":"array","items":{"type":"string"},"description":"Remove album data that matches any case-insensitive string from this list when scrobbling,\n\nFor certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use \"Soundcloud\" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake name to this list. Multi-scrobbler will still scrobble the track, just without the bad data.","examples":[["Soundcloud","Mixcloud"]],"default":["Soundcloud"]}},"title":"MopidyData"},"ListenBrainzSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenBrainzSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `listenbrainz.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["listenbrainz"]}},"required":["data","type"],"title":"ListenBrainzSourceAIOConfig"},"ListenBrainzSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzSourceData"},"JRiverSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JRiverData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jriver"]}},"required":["data","type"],"title":"JRiverSourceAIOConfig"},"JRiverData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the JRiver HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:52199/MCWS/v1/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `52199`\n* Path => `/MCWS/v1/`","examples":["http://localhost:52199/MCWS/v1/"],"default":"http://localhost:52199/MCWS/v1/"},"username":{"type":"string","description":"If you have enabled authentication, the username you set"},"password":{"type":"string","description":"If you have enabled authentication, the password you set"}},"required":["url"],"title":"JRiverData"},"KodiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KodiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["kodi"]}},"required":["data","type"],"title":"KodiSourceAIOConfig"},"KodiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Kodi HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:8080/jsonrpc`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `8080`\n* Path => `/jsonrpc`","examples":["http://localhost:8080/jsonrpc"],"default":"http://localhost:8080/jsonrpc"},"username":{"type":"string","description":"The username set for Remote Control via Web Sever"},"password":{"type":"string","description":"The password set for Remote Control via Web Sever"}},"required":["url","username","password"],"title":"KodiData"},"WebScrobblerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/WebScrobblerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["webscrobbler"]}},"required":["type"],"title":"WebScrobblerSourceAIOConfig"},"WebScrobblerData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIn WebScrobbler's Webhook you must set an 'API URL'. All MS WebScrobbler sources must start like:\n\nhttp://localhost:9078/api/webscrobbler\n\nIf you are using multiple WebScrobbler sources (scrobbles for many users) you must use a slug to match Sources with each users extension.\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/webscrobbler/usera\n* slug: 'userb' => API URL: http://localhost:9078/api/webscrobbler/userb\n\nIf no slug is found from an extension's incoming webhook event the first WebScrobbler source without a slug will be used"},"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Block scrobbling from specific WebScrobbler Connectors","examples":[["youtube"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only allow scrobbling from specific WebScrobbler Connectors","examples":[["mixcloud","soundcloud","bandcamp"]]}},"title":"WebScrobblerData"},"ChromecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ChromecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["chromecast"]}},"required":["data","type"],"title":"ChromecastSourceAIOConfig"},"ChromecastData":{"type":"object","properties":{"blacklistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"whitelistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any cast device that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"blacklistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any application that START WITH these values, case-insensitive","examples":[["spotify","pandora"]]},"whitelistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any application that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","pandora"]]},"useAvahi":{"type":"boolean","description":"Try to use Avahi and avahi-browse to resolve mDNS devices instead of native mDNS querying\n\nUseful for docker (alpine) container where mDNS resolution is not yet supported. Avahi socket must be exposed to the container and avahi-tools must be installed.","default":false},"useAutoDiscovery":{"type":"boolean","description":"Use mDNS to discovery Google Cast devices on your next automatically?\n\nIf not explicitly set then it is TRUE if `devices` is not set"},"devices":{"type":"array","items":{"$ref":"#/definitions/ChromecastDeviceInfo"},"description":"A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set"},"allowUnknownMedia":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Chromecast Apps report a \"media type\" in the status info returned for whatever is currently playing\n\n* If set to TRUE then Music AND Generic/Unknown media will be tracked for ALL APPS\n* If set to FALSE then only media explicitly typed as Music will be tracked for ALL APPS\n* If set to a list then only Apps whose name contain one of these values, case-insensitive, will have Music AND Generic/Unknown tracked\n\nSee https://developers.google.com/cast/docs/media/messages#MediaInformation \"metadata\" property","default":false},"forceMediaRecognitionOn":{"type":"array","items":{"type":"string"},"description":"Media provided by any App whose name is listed here will ALWAYS be tracked, regardless of the \"media type\" reported\n\nApps will be recognized if they CONTAIN any of these values, case-insensitive"}},"title":"ChromecastData"},"ChromecastDeviceInfo":{"type":"object","properties":{"name":{"type":"string","description":"A friendly name to identify this device","examples":["MySmartTV"]},"address":{"type":"string","description":"The IP address of the device","examples":["192.168.0.115"]}},"required":["name","address"],"title":"ChromecastDeviceInfo"},"MalojaSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MalojaSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `maloja.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["maloja"]}},"required":["data","type"],"title":"MalojaSourceAIOConfig"},"MalojaSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaSourceData"},"MusikcubeSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusikcubeData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musikcube"]}},"required":["data","type"],"title":"MusikcubeSourceAIOConfig"},"MusikcubeData":{"type":"object","properties":{"url":{"type":"string","description":"URL of the Musikcube Websocket (Metadata) server to connect to\n\nYou MUST have enabled 'metadata' server and set a password: https://github.com/clangen/musikcube/wiki/remote-api-documentation\n * musikcube -> settings -> server setup\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `7905`","examples":["ws://localhost:7905"],"default":"ws://localhost:7905"},"password":{"type":"string","description":"Password set in Musikcube https://github.com/clangen/musikcube/wiki/remote-api-documentation\n\n* musikcube -> settings -> server setup -> password"},"device_id":{"type":"string"}},"required":["password"],"title":"MusikcubeData"},"MusicCastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusicCastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musiccast"]}},"required":["data","type"],"title":"MusicCastSourceAIOConfig"},"MusicCastData":{"type":"object","properties":{"url":{"type":"string","description":"The host or URL of the YamahaExtendedControl endpoint to use","examples":[["192.168.0.101","http://192.168.0.101/YamahaExtendedControl"]]}},"required":["url"],"title":"MusicCastData"},"MPDSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPDData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/MPDSourceOptions"},"type":{"type":"string","enum":["mpd"]}},"required":["data","options","type"],"title":"MPDSourceAIOConfig"},"MPDData":{"type":"object","properties":{"url":{"type":"string","description":"URL:PORT of the MPD server to connect to\n\nTo use this you must have TCP connections enabled for your MPD server https://mpd.readthedocs.io/en/stable/user.html#client-connections","examples":["localhost:6600"],"default":"localhost:6600"},"path":{"type":"string","description":"If using socket specify the path instead of url.\n\ntrailing `~` is replaced by your home directory"},"password":{"type":"string","description":"Password for the server, if set https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords"}},"title":"MPDData"},"MPDSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"MPDSourceOptions"},"VLCSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/VLCData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/VLCSourceOptions"},"type":{"type":"string","enum":["vlc"]}},"required":["data","type"],"title":"VLCSourceAIOConfig"},"VLCData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL:PORT of the VLC server to connect to\n\nTo use this you must have the Web (http) interface module enabled and a password set https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc","examples":["localhost:8080"],"default":"localhost:8080"},"password":{"type":"string","description":"Password for the server"}},"required":["password"],"title":"VLCData"},"VLCSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"filenamePatterns":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A list of regular expressions to use to extract metadata (title, album, artist) from a filename\n\nUsed when VLC reports only the filename for the current audio track"},"logFilenamePatterns":{"type":"boolean","description":"Log to DEBUG when a filename-only track is matched or not matched by filenamePatterns","default":false},"dumpVlcMetadata":{"type":"boolean","description":"Dump all the metadata VLC reports for an audio track to DEBUG.\n\nUse this if reporting an issue with VLC not correctly capturing metadata for a track.","default":false}},"title":"VLCSourceOptions"},"IcecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/IcecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/IcecastSourceOptions"},"type":{"type":"string","enum":["icecast"]}},"required":["data","type"],"title":"IcecastSourceAIOConfig"},"IcecastData":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/definitions/IcecastSource"}},"icestatsEndpoint":{"type":"string"},"statsEndpoint":{"type":"string"},"nextsongsEndpoint":{"type":"string"},"sevenhtmlEndpoint":{"type":"string"},"icyMetaInt":{"type":"number"},"url":{"type":"string","description":"The Icecast stream URL"}},"required":["url"],"title":"IcecastData"},"IcecastSource":{"type":"string","enum":["icy","ogg","icestats","stats","sevenhtml","nextsongs"],"title":"IcecastSource"},"IcecastSourceOptions":{"type":"object","properties":{"systemScrobble":{"type":"boolean","description":"For Sources that support manual listening, should MS default to scrobbling when no user interaction has occurred?\n\nIf not specified MS will use a Source's specific behavior, see Source's documentation."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"IcecastSourceOptions"},"AzuracastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/AzuracastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["azuracast"]}},"required":["data","type"],"title":"AzuracastSourceAIOConfig"},"AzuracastData":{"type":"object","properties":{"url":{"type":"string","description":"Base URL of the Azuracast instance\n\nThis does NOT include the station. If a station is included it will be ignored. Use `station` field to specify station, if necessary","examples":["https://radio.mydomain.tld","http://localhost:80"]},"station":{"type":"string","description":"The specific station to monitor\n\nScrobbling will only occur if any of the monitor conditions are met AND the station is ONLINE.\n\nTo monitor multiple stations create a Source for each station.","examples":["my-station-1"]},"monitorWhenListeners":{"type":["boolean","number"],"description":"Only activate scrobble monitoring if station\n\n* `true` => has any current listeners\n* `number` => has EQUAL TO or MORE THAN X number of listeners"},"monitorWhenLive":{"type":"boolean","description":"Only activate scrobble monitoring if station has a live DJ/Streamer","default":true},"apiKey":{"type":"string","description":"API Key used to access data about private streams\n\nhttps://www.azuracast.com/docs/developers/apis/#api-authentication"}},"required":["url","station"],"title":"AzuracastData"},"KoitoSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KoitoSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `koito.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["koito"]}},"required":["data","type"],"title":"KoitoSourceAIOConfig"},"KoitoSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoSourceData"},"TealSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/TealSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/TealSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"Should always be `souce` when using Tealfm as a Source","default":"source","examples":["source"]},"type":{"type":"string","enum":["tealfm"]}},"required":["data","type"],"title":"TealSourceAIOConfig"},"TealSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealSourceData"},"TealSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealSourceOptions"},"RockskySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/RockskySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/RockskySourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `rocksky.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["rocksky"]}},"required":["data","type"],"title":"RockskySourceAIOConfig"},"RockskySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockskySourceData"},"RockskySourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockskySourceOptions"},"ClientAIOConfig":{"anyOf":[{"$ref":"#/definitions/MalojaClientAIOConfig"},{"$ref":"#/definitions/LastfmClientAIOConfig"},{"$ref":"#/definitions/ListenBrainzClientAIOConfig"},{"$ref":"#/definitions/KoitoClientAIOConfig"},{"$ref":"#/definitions/TealClientAIOConfig"},{"$ref":"#/definitions/RockSkyClientAIOConfig"}],"title":"ClientAIOConfig"},"MalojaClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/MalojaClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Maloja as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["maloja"]}},"required":["data","name","type"],"title":"MalojaClientAIOConfig"},"MalojaClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaClientData"},"CommonClientOptions":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonClientOptions"},"LastfmClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/LastfmData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/LastfmClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using LastFM as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["lastfm"]}},"required":["data","name","type"],"title":"LastfmClientAIOConfig"},"LastfmData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]}},"required":["apiKey","secret"],"title":"LastfmData"},"LastfmClientOptions":{"type":"object","properties":{"nowPlaying":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Configure if this Client should report Now Playing from Sources that can scrobble to it\n\n* `true` (default) => Report Now Playing from any eligible Source. \n * If multiple Sources are Playing then reported Play is based on alphabetical order of Source names\n* `false` => Do not report Now Playing\n* `string` list => list of Source `names` that should be allowed to report Now Playing. Order of list determine priority of Play to Report.","default":true},"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"LastfmClientOptions"},"ListenBrainzClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/ListenBrainzClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Listenbrainz as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["listenbrainz"]}},"required":["data","name","type"],"title":"ListenBrainzClientAIOConfig"},"ListenBrainzClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzClientData"},"KoitoClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/KoitoClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Koito as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["koito"]}},"required":["data","name","type"],"title":"KoitoClientAIOConfig"},"KoitoClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoClientData"},"TealClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/TealClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/TealClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Tealfm as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["tealfm"]}},"required":["data","name","type"],"title":"TealClientAIOConfig"},"TealClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealClientData"},"TealClientOptions":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealClientOptions"},"RockSkyClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/RockSkyClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/RockSkyClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using RockSky as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["rocksky"]}},"required":["data","name","type"],"title":"RockSkyClientAIOConfig"},"RockSkyClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockSkyClientData"},"RockSkyClientOptions":{"type":"object","properties":{"nowPlaying":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Configure if this Client should report Now Playing from Sources that can scrobble to it\n\n* `true` (default) => Report Now Playing from any eligible Source. \n * If multiple Sources are Playing then reported Play is based on alphabetical order of Source names\n* `false` => Do not report Now Playing\n* `string` list => list of Source `names` that should be allowed to report Now Playing. Order of list determine priority of Play to Report.","default":true},"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockSkyClientOptions"},"WebhookConfig":{"anyOf":[{"$ref":"#/definitions/GotifyConfig"},{"$ref":"#/definitions/NtfyConfig"},{"$ref":"#/definitions/AppriseConfig"}],"title":"WebhookConfig"},"GotifyConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"url":{"type":"string","description":"The URL of the Gotify server. Same URL that would be used to reach the Gotify UI","examples":["http://192.168.0.100:8078"]},"token":{"type":"string","description":"The token created for this Application in Gotify","examples":["AQZI58fA.rfSZbm"]},"priorities":{"$ref":"#/definitions/PrioritiesConfig","description":"Priority of messages\n\n* Info -> 5\n* Warn -> 7\n* Error -> 10"}},"required":["token","type","url"],"title":"GotifyConfig"},"PrioritiesConfig":{"type":"object","properties":{"info":{"type":"number","examples":[5]},"warn":{"type":"number","examples":[7]},"error":{"type":"number","examples":[10]}},"required":["info","warn","error"],"title":"PrioritiesConfig"},"NtfyConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"url":{"type":"string","description":"The URL of the Ntfy server","examples":["http://192.168.0.100:8078"]},"topic":{"type":"string","description":"The topic mutli-scrobbler should POST to"},"username":{"type":"string","description":"Required if topic is protected"},"password":{"type":"string","description":"Required if topic is protected"},"token":{"type":"string","description":"Use instead of username/password, required if topic is protected"},"priorities":{"$ref":"#/definitions/PrioritiesConfig","description":"Priority of messages\n\n* Info -> 3\n* Warn -> 4\n* Error -> 5"}},"required":["topic","type","url"],"title":"NtfyConfig"},"AppriseConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"host":{"type":"string","description":"The URL of the apprise-api server","examples":["http://192.168.0.100:8078"]},"urls":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"If using [Stateless Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#stateless-solution) the Apprise config URL(s) to send"},"keys":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"If using [Persistent Store Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#persistent-storage-solution) the Configuration ID(s) to send to\n\nNote: If multiple keys are defined then MS will attempt to POST to each one individually"},"tags":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Optional [tag(s)](https://github.com/caronc/apprise-api?tab=readme-ov-file#tagging) to send in the notification payload"}},"required":["host","type"],"title":"AppriseConfig"},"LogOptions":{"type":"object","properties":{"level":{"$ref":"#/definitions/LogLevel","description":"Specify the minimum log level for all log outputs without their own level specified.\n\nDefaults to env `LOG_LEVEL` or `info` if not specified.","default":"info"},"file":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false},{"$ref":"#/definitions/FileLogOptions"}],"description":"Specify the minimum log level to output to rotating files or file output options. If `false` no log files will be created."},"console":{"$ref":"#/definitions/LogLevel","description":"Specify the minimum log level streamed to the console (or docker container)"}},"description":"Configure log levels and file options for an AppLogger.\n\n```ts\nconst infoLogger = loggerApp({\n level: 'info' // console and file will log any levels `info` and above\n});\n\nconst logger = loggerApp({\n console: 'debug', // console will log `debug` and higher\n file: 'warn' // file will log `warn` and higher\n});\n\nconst fileLogger = loggerRollingApp({\n console: 'debug', // console will log `debug` and higher\n file: {\n level: 'warn', // file will log `warn` and higher\n path: '/my/cool/path/output.log', // optionally, output to log file at this path\n frequency: 'hourly', // optionally, rotate hourly\n }\n});\n```","title":"LogOptions"},"CacheConfigOptions":{"type":"object","properties":{"metadata":{"$ref":"#/definitions/CacheMetadataConfig"},"scrobble":{"$ref":"#/definitions/CacheScrobbleConfig"},"auth":{"$ref":"#/definitions/CacheAuthConfig"}},"title":"CacheConfigOptions"},"CacheMetadataConfig":{"$ref":"#/definitions/CacheConfig%3CCacheMetadataProvider%3E","title":"CacheMetadataConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheMetadataProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheMetadataProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheMetadataProvider"},"CacheProvider":{"type":["string","boolean"],"enum":["memory","valkey","file",false],"title":"CacheProvider"},"CacheScrobbleConfig":{"$ref":"#/definitions/CacheConfig%3CCacheScrobbleProvider%3E","title":"CacheScrobbleConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheScrobbleProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheScrobbleProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheScrobbleProvider"},"CacheAuthConfig":{"$ref":"#/definitions/CacheConfig%3CCacheAuthProvider%3E","title":"CacheAuthConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheAuthProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheAuthProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheAuthProvider"}}} \ No newline at end of file +{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"sourceDefaults":{"$ref":"#/definitions/SourceDefaults"},"clientDefaults":{"$ref":"#/definitions/ClientDefaults"},"sources":{"type":"array","items":{"$ref":"#/definitions/SourceAIOConfig"}},"clients":{"type":"array","items":{"$ref":"#/definitions/ClientAIOConfig"}},"webhooks":{"type":"array","items":{"$ref":"#/definitions/WebhookConfig"}},"port":{"type":"number","description":"Set the port the multi-scrobbler UI will be served from","default":9078,"examples":[9078]},"baseUrl":{"type":"string","description":"Set the Base URL the application should assume the UI is served from.\n\nThis will affect how default redirect URLs are generated (spotify, lastfm, deezer) and some logging messages.\n\nIt will NOT set the actual interface/IP that the application is listening on.\n\nThis can also be set using the BASE_URL environmental variable.","default":"http://localhost","examples":["http://localhost","http://192.168.0.101","https://ms.myDomain.tld"]},"logging":{"$ref":"#/definitions/LogOptions"},"disableWeb":{"type":"boolean","description":"Disable web server from running/listening on port.\n\nThis will also make any ingress sources (Plex, Jellyfin, Tautulli, etc...) unusable"},"debugMode":{"type":"boolean","description":"Enables ALL relevant logging and debug options for all sources/clients, when none are defined.\n\nThis is a convenience shortcut for enabling all output needed to troubleshoot an issue and does not need to be on for normal operation.\n\nIt can also be enabled with the environmental variable DEBUG_MODE=true","default":false,"examples":[false]},"cache":{"$ref":"#/definitions/CacheConfigOptions"}},"definitions":{"SourceDefaults":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"SourceDefaults"},"LogLevel":{"type":"string","enum":["silent","fatal","error","warn","info","log","verbose","debug"],"description":"Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.","title":"LogLevel"},"FileLogOptions":{"type":"object","properties":{"timestamp":{"type":"string","enum":["unix","iso","auto"],"description":"For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime","default":"auto"},"size":{"type":["number","string"],"description":"The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.","default":"10MB"},"frequency":{"anyOf":[{"type":"string","enum":["daily"]},{"type":"string","enum":["hourly"]},{"type":"number"}],"description":"The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.","default":"daily"},"path":{"anyOf":[{"type":"string"},{"$comment":"() => string"}],"description":"The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD","default":"./logs/app.log"},"level":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false}],"description":"Specify the minimum log level to output to rotating files. If `false` no log files will be created."}},"title":"FileLogOptions"},"ScrobbleThresholds":{"type":"object","properties":{"duration":{"type":["number","null"],"description":"The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.","default":240,"examples":[240]},"percent":{"type":["number","null"],"description":"The percentage (as an integer) of a track that should have been seen played before it should be scrobbled. Only used if the Source provides information about how long the track is.\n\nSet to null to disable.\n\nNOTE: This should be used with care when the Source is a \"polling\" type (has an 'interval' property). If the track is short and the interval is too high MS may ignore the track if percentage is high because it had not \"seen\" the track for long enough from first discovery, even if you have been playing the track for longer.","default":50,"examples":[50]}},"title":"ScrobbleThresholds"},"PlayTransformOptions":{"type":"object","properties":{"log":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["all"]}]},"preCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"compare":{"type":"object","properties":{"candidate":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"existing":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}}},"postCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}},"title":"PlayTransformOptions"},"PlayTransformPartsConfig":{"anyOf":[{"$ref":"#/definitions/PlayTransformPartsArray%3CSearchAndReplaceTerm%3E"},{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"}],"title":"PlayTransformPartsConfig"},"PlayTransformPartsArray":{"type":"array","items":{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"},"title":"PlayTransformPartsArray"},"PlayTransformParts":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"title":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"artists":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"album":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}}},"title":"PlayTransformParts"},"WhenConditionsConfig":{"$ref":"#/definitions/WhenConditions%3Cstring%3E","title":"WhenConditionsConfig"},"WhenConditions":{"type":"array","items":{"$ref":"#/definitions/WhenParts%3Cstring%3E"},"title":"WhenConditions"},"WhenParts":{"$ref":"#/definitions/PlayTransformPartsAtomic%3Cstring%3E","title":"WhenParts"},"PlayTransformPartsAtomic":{"type":"object","properties":{"title":{"type":"string"},"artists":{"type":"string"},"album":{"type":"string"}},"title":"PlayTransformPartsAtomic"},"SearchAndReplaceTerm":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/ConditionalSearchAndReplaceTerm"}],"title":"SearchAndReplaceTerm"},"ConditionalSearchAndReplaceTerm":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"search":{},"replace":{}},"required":["search","replace"],"title":"ConditionalSearchAndReplaceTerm"},"ClientDefaults":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"ClientDefaults"},"MatchLoggingOptions":{"type":"object","properties":{"onNoMatch":{"type":"boolean","description":"Log to DEBUG when a new track does NOT match an existing scrobble","default":false,"examples":[false]},"onMatch":{"type":"boolean","description":"Log to DEBUG when a new track DOES match an existing scrobble","default":false,"examples":[false]},"confidenceBreakdown":{"type":"boolean","description":"Include confidence breakdowns in track match logging, if applicable","default":false,"examples":[false]}},"description":"Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.","title":"MatchLoggingOptions"},"SourceAIOConfig":{"anyOf":[{"$ref":"#/definitions/SpotifySourceAIOConfig"},{"$ref":"#/definitions/PlexSourceAIOConfig"},{"$ref":"#/definitions/PlexApiSourceAIOConfig"},{"$ref":"#/definitions/TautulliSourceAIOConfig"},{"$ref":"#/definitions/DeezerSourceAIOConfig"},{"$ref":"#/definitions/DeezerInternalAIOConfig"},{"$ref":"#/definitions/ListenbrainzEndpointSourceAIOConfig"},{"$ref":"#/definitions/LastFMEndpointSourceAIOConfig"},{"$ref":"#/definitions/SubsonicSourceAIOConfig"},{"$ref":"#/definitions/JellySourceAIOConfig"},{"$ref":"#/definitions/JellyApiSourceAIOConfig"},{"$ref":"#/definitions/LastFmSouceAIOConfig"},{"$ref":"#/definitions/YTMusicSourceAIOConfig"},{"$ref":"#/definitions/MPRISSourceAIOConfig"},{"$ref":"#/definitions/MopidySourceAIOConfig"},{"$ref":"#/definitions/ListenBrainzSourceAIOConfig"},{"$ref":"#/definitions/JRiverSourceAIOConfig"},{"$ref":"#/definitions/KodiSourceAIOConfig"},{"$ref":"#/definitions/WebScrobblerSourceAIOConfig"},{"$ref":"#/definitions/ChromecastSourceAIOConfig"},{"$ref":"#/definitions/MalojaSourceAIOConfig"},{"$ref":"#/definitions/MusikcubeSourceAIOConfig"},{"$ref":"#/definitions/MusicCastSourceAIOConfig"},{"$ref":"#/definitions/MPDSourceAIOConfig"},{"$ref":"#/definitions/VLCSourceAIOConfig"},{"$ref":"#/definitions/IcecastSourceAIOConfig"},{"$ref":"#/definitions/AzuracastSourceAIOConfig"},{"$ref":"#/definitions/KoitoSourceAIOConfig"},{"$ref":"#/definitions/TealSourceAIOConfig"},{"$ref":"#/definitions/RockskySourceAIOConfig"}],"title":"SourceAIOConfig"},"SpotifySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SpotifySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["spotify"]}},"required":["data","type"],"title":"SpotifySourceAIOConfig"},"SpotifySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)\n\nIt is unlikely you should need to change this unless you scrobble many very short tracks often\n\nReading:\n* https://developer.spotify.com/documentation/web-api/guides/rate-limits/\n* https://medium.com/mendix/limiting-your-amount-of-calls-in-mendix-most-of-the-time-rest-835dde55b10e\n * Rate limit may ~180 req/min\n* https://community.spotify.com/t5/Spotify-for-Developers/Web-API-ratelimit/m-p/5503150/highlight/true#M7930\n * Informally indicated as 20 req/sec? Probably for burstiness","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"spotify client id","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"clientSecret":{"type":"string","description":"spotify client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"spotify redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/callback","examples":["http://localhost:9078/callback"]}},"required":["clientId","clientSecret"],"title":"SpotifySourceData"},"CommonSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonSourceOptions"},"PlexSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","type"],"title":"PlexSourceAIOConfig"},"PlexSourceData":{"type":"object","properties":{"user":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"libraries":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of libraries to scrobble tracks from\n\nIf none are provided tracks from all libraries will be scrobbled","examples":[["Audio","Music"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName"]]}},"title":"PlexSourceData"},"PlexApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/PlexApiOptions"},"type":{"type":"string","enum":["plex"]}},"required":["data","options","type"],"title":"PlexApiSourceAIOConfig"},"PlexApiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"token":{"type":"string"},"url":{"type":"string","description":"http(s)://HOST:PORT of the Plex server to connect to"},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"}},"required":["url"],"title":"PlexApiData"},"PlexApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"ignoreInvalidCert":{"type":"boolean","description":"Ignore invalid cert errors when connecting to Plex\n\nUseful for Plex servers using \"Required\" Secure Connections with self-signed certificates\n\nDo not enable unless you know you need this.","default":false}},"title":"PlexApiOptions"},"TautulliSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["tautulli"]}},"required":["data","type"],"title":"TautulliSourceAIOConfig"},"DeezerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerSourceAIOConfig"},"DeezerData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"deezer client id","examples":["a89cba1569901a0671d5a9875fed4be1"]},"clientSecret":{"type":"string","description":"deezer client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/deezer/callback","examples":["http://localhost:9078/deezer/callback"]}},"required":["clientId","clientSecret"],"title":"DeezerData"},"DeezerInternalAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerInternalData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/DeezerInternalSourceOptions"},"type":{"type":"string","enum":["deezer"]}},"required":["data","type"],"title":"DeezerInternalAIOConfig"},"DeezerInternalData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"arl":{"type":"string","description":"ARL retrieved from Deezer response header"},"userAgent":{"type":"string","description":"User agent","default":"Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0"}},"required":["arl"],"title":"DeezerInternalData"},"DeezerInternalSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"fuzzyDiscoveryIgnore":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["aggressive"]}]}},"title":"DeezerInternalSourceOptions"},"ListenbrainzEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenbrainzEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlz"]}},"required":["type"],"title":"ListenbrainzEndpointSourceAIOConfig"},"ListenbrainzEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Listenbrainz endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/listenbrainz/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/listenbrainz/originb\n\nIf no slug is found from an extension's incoming webhook event the first Listenbrainz source without a slug will be used"},"token":{"type":["string","null"],"description":"If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source\n\nSee: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#add-the-user-token-to-your-requests"}},"title":"ListenbrainzEndpointData"},"LastFMEndpointSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFMEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["endpointlfm"]}},"required":["type"],"title":"LastFMEndpointSourceAIOConfig"},"LastFMEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Last.fm endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/lastfm/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/lastfm/originb\n\nIf no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used"}},"title":"LastFMEndpointData"},"SubsonicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SubsonicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["subsonic"]}},"required":["data","type"],"title":"SubsonicSourceAIOConfig"},"SubsonicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the subsonic media server to query","examples":["http://airsonic.local"]},"user":{"type":"string","description":"Username to login to the server with","examples":[["MyUser"]]},"password":{"type":"string","description":"Password for the user to login to the server with","examples":["MyPassword"]},"ignoreTlsErrors":{"type":"boolean","description":"If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection\n\nWARNING: This should be used with caution as your traffic may not be encrypted.","default":false},"legacyAuthentication":{"type":"boolean","description":"Older Subsonic versions, and some badly implemented servers (Nextcloud), use legacy authentication which sends your password in CLEAR TEXT. This is less secure than the newer, recommended hashing authentication method but in some cases it is needed. See \"Authentication\" section here => https://www.subsonic.org/pages/api.jsp\n\nIf this option is not specified it will be turned on if the subsonic server responds with error code 41 \"Token authentication not supported for LDAP users.\" -- See Error Handling section => https://www.subsonic.org/pages/api.jsp","default":false},"usersAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf undefined or an empty string/list MS will scrobble activity from all users"}},"required":["url","user","password"],"title":"SubsonicData"},"JellySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","type"],"title":"JellySourceAIOConfig"},"JellyData":{"type":"object","properties":{"users":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName1"]]}},"title":"JellyData"},"JellyApiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/JellyApiOptions"},"type":{"type":"string","enum":["jellyfin"]}},"required":["data","options","type"],"title":"JellyApiSourceAIOConfig"},"JellyApiData":{"type":"object","properties":{"url":{"type":"string","description":"HOST:PORT of the Jellyfin server to connect to"},"user":{"type":"string","description":"The username of the user to authenticate for or track scrobbles for"},"password":{"type":"string","description":"Password of the username to authenticate for\n\nRequired if `apiKey` is not provided."},"apiKey":{"type":"string","description":"API Key to authenticate with.\n\nRequired if `password` is not provided."},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"},"additionalAllowedLibraryTypes":{"type":"array","items":{},"description":"Allow MS to scrobble audio media in libraries classified other than 'music'\n\n`librariesAllow` will achieve the same result as this but this is more convenient if you do not want to explicitly list every library name or are only using `librariesBlock`"},"allowUnknown":{"type":"boolean","description":"Force media with a type of \"Unknown\" to be counted as Audio","default":false},"frontendUrlOverride":{"type":"string","description":"HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from)\nIf unspecified it will use the normal server HOST and PORT from the `url`\nNecessary if you are using a reverse proxy or other network configuration that prevents the frontend from accessing the server directly\n\nENV: JELLYFIN_FRONTEND_URL_OVERRIDE"}},"required":["url","user"],"title":"JellyApiData"},"JellyApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"JellyApiOptions"},"LastFmSouceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFmSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["lastfm"]}},"required":["data","type"],"title":"LastFmSouceAIOConfig"},"LastFmSourceData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]},"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"}},"required":["apiKey","secret"],"title":"LastFmSourceData"},"YTMusicSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/YTMusicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"type":"object","properties":{"logAuth":{"type":"boolean","description":"When true MS will log to DEBUG all of the credentials data it receives from YTM"},"logDiff":{"type":"boolean","description":"Always log history diff\n\nBy default MS will log to `WARN` if history diff is inconsistent but does not log if diff is expected (on new tracks found)\nSet this to `true` to ALWAYS log diff on new tracks. Expected diffs will log to `DEBUG` and inconsistent diffs will continue to log to `WARN`","default":false},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}}},"type":{"type":"string","enum":["ytmusic"]}},"required":["type"],"title":"YTMusicSourceAIOConfig"},"YTMusicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"cookie":{"type":"string","description":"The cookie retrieved from the Request Headers of music.youtube.com after logging in.\n\nSee https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers for how to retrieve this value.","examples":["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]},"clientId":{"type":"string","description":"Google Cloud Console project OAuth Client ID\n\nGenerated from a custom OAuth Client, see docs"},"clientSecret":{"type":"string","description":"Google Cloud Console project OAuth Client Secret\n\nGenerated from a custom OAuth Client, see docs"},"redirectUri":{"type":"string","description":"Google Cloud Console project OAuth Client Authorized redirect URI\n\nGenerated from a custom OAuth Client, see docs. multi-scrobbler will generate a default based on BASE_URL.\nOnly specify this if the default does not work for you."},"innertubeOptions":{"$ref":"#/definitions/InnertubeOptions","description":"Additional options for authorization and tailoring YTM client"}},"title":"YTMusicData"},"InnertubeOptions":{"type":"object","properties":{"po_token":{"type":"string","description":"Proof of Origin token\n\nMay be required if YTM starts returning 403"},"visitor_data":{"type":"string","description":"Visitor ID value found in VISITOR_INFO1_LIVE or visitorData cookie\n\nMay be required if YTM starts returning 403"},"account_index":{"type":"number","description":"If account login results in being able to choose multiple account, use a zero-based index to choose which one to monitor","examples":[0,1]},"location":{"type":"string"},"lang":{"type":"string"},"generate_session_locally":{"type":"boolean"},"device_category":{"type":"string"},"client_type":{"type":"string"},"timezone":{"type":"string"}},"title":"InnertubeOptions"},"MPRISSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPRISData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mpris"]}},"required":["data","type"],"title":"MPRISSourceAIOConfig"},"MPRISData":{"type":"object","properties":{"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any players that START WITH these values, case-insensitive","examples":[["spotify","vlc"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","vlc"]]}},"title":"MPRISData"},"MopidySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MopidyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["mopidy"]}},"required":["data","type"],"title":"MopidySourceAIOConfig"},"MopidyData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Mopidy HTTP server to connect to\n\nYou MUST have Mopidy-HTTP extension enabled: https://mopidy.com/ext/http\n\nmulti-scrobbler connects to the WebSocket endpoint that ultimately looks like this => `ws://localhost:6680/mopidy/ws/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `6680`\n* Path => `/mopidy/ws/`","examples":["ws://localhost:6680/mopidy/ws/"],"default":"ws://localhost:6680/mopidy/ws/"},"uriBlacklist":{"type":"array","items":{"type":"string"},"description":"Do not scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Don't scrobble tracks from soundcloud by adding 'soundcloud' to this list.\n\nList is ignored if uriWhitelist is used."},"uriWhitelist":{"type":"array","items":{"type":"string"},"description":"Only scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Only scrobble tracks from soundcloud by adding 'soundcloud' to this list."},"albumBlacklist":{"type":"array","items":{"type":"string"},"description":"Remove album data that matches any case-insensitive string from this list when scrobbling,\n\nFor certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use \"Soundcloud\" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake name to this list. Multi-scrobbler will still scrobble the track, just without the bad data.","examples":[["Soundcloud","Mixcloud"]],"default":["Soundcloud"]}},"title":"MopidyData"},"ListenBrainzSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenBrainzSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `listenbrainz.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["listenbrainz"]}},"required":["data","type"],"title":"ListenBrainzSourceAIOConfig"},"ListenBrainzSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzSourceData"},"JRiverSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JRiverData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["jriver"]}},"required":["data","type"],"title":"JRiverSourceAIOConfig"},"JRiverData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the JRiver HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:52199/MCWS/v1/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `52199`\n* Path => `/MCWS/v1/`","examples":["http://localhost:52199/MCWS/v1/"],"default":"http://localhost:52199/MCWS/v1/"},"username":{"type":"string","description":"If you have enabled authentication, the username you set"},"password":{"type":"string","description":"If you have enabled authentication, the password you set"}},"required":["url"],"title":"JRiverData"},"KodiSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KodiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["kodi"]}},"required":["data","type"],"title":"KodiSourceAIOConfig"},"KodiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Kodi HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:8080/jsonrpc`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `8080`\n* Path => `/jsonrpc`","examples":["http://localhost:8080/jsonrpc"],"default":"http://localhost:8080/jsonrpc"},"username":{"type":"string","description":"The username set for Remote Control via Web Sever"},"password":{"type":"string","description":"The password set for Remote Control via Web Sever"}},"required":["url","username","password"],"title":"KodiData"},"WebScrobblerSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/WebScrobblerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["webscrobbler"]}},"required":["type"],"title":"WebScrobblerSourceAIOConfig"},"WebScrobblerData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIn WebScrobbler's Webhook you must set an 'API URL'. All MS WebScrobbler sources must start like:\n\nhttp://localhost:9078/api/webscrobbler\n\nIf you are using multiple WebScrobbler sources (scrobbles for many users) you must use a slug to match Sources with each users extension.\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/webscrobbler/usera\n* slug: 'userb' => API URL: http://localhost:9078/api/webscrobbler/userb\n\nIf no slug is found from an extension's incoming webhook event the first WebScrobbler source without a slug will be used"},"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Block scrobbling from specific WebScrobbler Connectors","examples":[["youtube"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only allow scrobbling from specific WebScrobbler Connectors","examples":[["mixcloud","soundcloud","bandcamp"]]}},"title":"WebScrobblerData"},"ChromecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ChromecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["chromecast"]}},"required":["data","type"],"title":"ChromecastSourceAIOConfig"},"ChromecastData":{"type":"object","properties":{"blacklistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"whitelistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any cast device that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"blacklistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any application that START WITH these values, case-insensitive","examples":[["spotify","pandora"]]},"whitelistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any application that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","pandora"]]},"useAvahi":{"type":"boolean","description":"Try to use Avahi and avahi-browse to resolve mDNS devices instead of native mDNS querying\n\nUseful for docker (alpine) container where mDNS resolution is not yet supported. Avahi socket must be exposed to the container and avahi-tools must be installed.","default":false},"useAutoDiscovery":{"type":"boolean","description":"Use mDNS to discovery Google Cast devices on your next automatically?\n\nIf not explicitly set then it is TRUE if `devices` is not set"},"devices":{"type":"array","items":{"$ref":"#/definitions/ChromecastDeviceInfo"},"description":"A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set"},"allowUnknownMedia":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Chromecast Apps report a \"media type\" in the status info returned for whatever is currently playing\n\n* If set to TRUE then Music AND Generic/Unknown media will be tracked for ALL APPS\n* If set to FALSE then only media explicitly typed as Music will be tracked for ALL APPS\n* If set to a list then only Apps whose name contain one of these values, case-insensitive, will have Music AND Generic/Unknown tracked\n\nSee https://developers.google.com/cast/docs/media/messages#MediaInformation \"metadata\" property","default":false},"forceMediaRecognitionOn":{"type":"array","items":{"type":"string"},"description":"Media provided by any App whose name is listed here will ALWAYS be tracked, regardless of the \"media type\" reported\n\nApps will be recognized if they CONTAIN any of these values, case-insensitive"}},"title":"ChromecastData"},"ChromecastDeviceInfo":{"type":"object","properties":{"name":{"type":"string","description":"A friendly name to identify this device","examples":["MySmartTV"]},"address":{"type":"string","description":"The IP address of the device","examples":["192.168.0.115"]}},"required":["name","address"],"title":"ChromecastDeviceInfo"},"MalojaSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MalojaSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `maloja.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["maloja"]}},"required":["data","type"],"title":"MalojaSourceAIOConfig"},"MalojaSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaSourceData"},"MusikcubeSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusikcubeData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musikcube"]}},"required":["data","type"],"title":"MusikcubeSourceAIOConfig"},"MusikcubeData":{"type":"object","properties":{"url":{"type":"string","description":"URL of the Musikcube Websocket (Metadata) server to connect to\n\nYou MUST have enabled 'metadata' server and set a password: https://github.com/clangen/musikcube/wiki/remote-api-documentation\n * musikcube -> settings -> server setup\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `7905`","examples":["ws://localhost:7905"],"default":"ws://localhost:7905"},"password":{"type":"string","description":"Password set in Musikcube https://github.com/clangen/musikcube/wiki/remote-api-documentation\n\n* musikcube -> settings -> server setup -> password"},"device_id":{"type":"string"}},"required":["password"],"title":"MusikcubeData"},"MusicCastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusicCastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["musiccast"]}},"required":["data","type"],"title":"MusicCastSourceAIOConfig"},"MusicCastData":{"type":"object","properties":{"url":{"type":"string","description":"The host or URL of the YamahaExtendedControl endpoint to use","examples":[["192.168.0.101","http://192.168.0.101/YamahaExtendedControl"]]}},"required":["url"],"title":"MusicCastData"},"MPDSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPDData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/MPDSourceOptions"},"type":{"type":"string","enum":["mpd"]}},"required":["data","options","type"],"title":"MPDSourceAIOConfig"},"MPDData":{"type":"object","properties":{"url":{"type":"string","description":"URL:PORT of the MPD server to connect to\n\nTo use this you must have TCP connections enabled for your MPD server https://mpd.readthedocs.io/en/stable/user.html#client-connections","examples":["localhost:6600"],"default":"localhost:6600"},"path":{"type":"string","description":"If using socket specify the path instead of url.\n\ntrailing `~` is replaced by your home directory"},"password":{"type":"string","description":"Password for the server, if set https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords"}},"title":"MPDData"},"MPDSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"MPDSourceOptions"},"VLCSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/VLCData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/VLCSourceOptions"},"type":{"type":"string","enum":["vlc"]}},"required":["data","type"],"title":"VLCSourceAIOConfig"},"VLCData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL:PORT of the VLC server to connect to\n\nTo use this you must have the Web (http) interface module enabled and a password set https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc","examples":["localhost:8080"],"default":"localhost:8080"},"password":{"type":"string","description":"Password for the server"}},"required":["password"],"title":"VLCData"},"VLCSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"filenamePatterns":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A list of regular expressions to use to extract metadata (title, album, artist) from a filename\n\nUsed when VLC reports only the filename for the current audio track"},"logFilenamePatterns":{"type":"boolean","description":"Log to DEBUG when a filename-only track is matched or not matched by filenamePatterns","default":false},"dumpVlcMetadata":{"type":"boolean","description":"Dump all the metadata VLC reports for an audio track to DEBUG.\n\nUse this if reporting an issue with VLC not correctly capturing metadata for a track.","default":false}},"title":"VLCSourceOptions"},"IcecastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/IcecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/IcecastSourceOptions"},"type":{"type":"string","enum":["icecast"]}},"required":["data","type"],"title":"IcecastSourceAIOConfig"},"IcecastData":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/definitions/IcecastSource"}},"icestatsEndpoint":{"type":"string"},"statsEndpoint":{"type":"string"},"nextsongsEndpoint":{"type":"string"},"sevenhtmlEndpoint":{"type":"string"},"icyMetaInt":{"type":"number"},"url":{"type":"string","description":"The Icecast stream URL"}},"required":["url"],"title":"IcecastData"},"IcecastSource":{"type":"string","enum":["icy","ogg","icestats","stats","sevenhtml","nextsongs"],"title":"IcecastSource"},"IcecastSourceOptions":{"type":"object","properties":{"systemScrobble":{"type":"boolean","description":"For Sources that support manual listening, should MS default to scrobbling when no user interaction has occurred?\n\nIf not specified MS will use a Source's specific behavior, see Source's documentation."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"IcecastSourceOptions"},"AzuracastSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/AzuracastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"type":{"type":"string","enum":["azuracast"]}},"required":["data","type"],"title":"AzuracastSourceAIOConfig"},"AzuracastData":{"type":"object","properties":{"url":{"type":"string","description":"Base URL of the Azuracast instance\n\nThis does NOT include the station. If a station is included it will be ignored. Use `station` field to specify station, if necessary","examples":["https://radio.mydomain.tld","http://localhost:80"]},"station":{"type":"string","description":"The specific station to monitor\n\nScrobbling will only occur if any of the monitor conditions are met AND the station is ONLINE.\n\nTo monitor multiple stations create a Source for each station.","examples":["my-station-1"]},"monitorWhenListeners":{"type":["boolean","number"],"description":"Only activate scrobble monitoring if station\n\n* `true` => has any current listeners\n* `number` => has EQUAL TO or MORE THAN X number of listeners"},"monitorWhenLive":{"type":"boolean","description":"Only activate scrobble monitoring if station has a live DJ/Streamer","default":true},"apiKey":{"type":"string","description":"API Key used to access data about private streams\n\nhttps://www.azuracast.com/docs/developers/apis/#api-authentication"}},"required":["url","station"],"title":"AzuracastData"},"KoitoSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KoitoSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `koito.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["koito"]}},"required":["data","type"],"title":"KoitoSourceAIOConfig"},"KoitoSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoSourceData"},"TealSourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/TealSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/TealSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"Should always be `souce` when using Tealfm as a Source","default":"source","examples":["source"]},"type":{"type":"string","enum":["tealfm"]}},"required":["data","type"],"title":"TealSourceAIOConfig"},"TealSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealSourceData"},"TealSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealSourceOptions"},"RockskySourceAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/RockskySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/RockskySourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `rocksky.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]},"type":{"type":"string","enum":["rocksky"]}},"required":["data","type"],"title":"RockskySourceAIOConfig"},"RockskySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockskySourceData"},"RockskySourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockskySourceOptions"},"ClientAIOConfig":{"anyOf":[{"$ref":"#/definitions/MalojaClientAIOConfig"},{"$ref":"#/definitions/LastfmClientAIOConfig"},{"$ref":"#/definitions/ListenBrainzClientAIOConfig"},{"$ref":"#/definitions/KoitoClientAIOConfig"},{"$ref":"#/definitions/TealClientAIOConfig"},{"$ref":"#/definitions/RockSkyClientAIOConfig"}],"title":"ClientAIOConfig"},"MalojaClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/MalojaClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Maloja as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["maloja"]}},"required":["data","name","type"],"title":"MalojaClientAIOConfig"},"MalojaClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaClientData"},"CommonClientOptions":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonClientOptions"},"LastfmClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/LastfmData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/LastfmClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using LastFM as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["lastfm"]}},"required":["data","name","type"],"title":"LastfmClientAIOConfig"},"LastfmData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]}},"required":["apiKey","secret"],"title":"LastfmData"},"LastfmClientOptions":{"type":"object","properties":{"nowPlaying":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Configure if this Client should report Now Playing from Sources that can scrobble to it\n\n* `true` (default) => Report Now Playing from any eligible Source. \n * If multiple Sources are Playing then reported Play is based on alphabetical order of Source names\n* `false` => Do not report Now Playing\n* `string` list => list of Source `names` that should be allowed to report Now Playing. Order of list determine priority of Play to Report.","default":true},"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"LastfmClientOptions"},"ListenBrainzClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/ListenBrainzClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Listenbrainz as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["listenbrainz"]}},"required":["data","name","type"],"title":"ListenBrainzClientAIOConfig"},"ListenBrainzClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzClientData"},"KoitoClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/KoitoClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/CommonClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Koito as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["koito"]}},"required":["data","name","type"],"title":"KoitoClientAIOConfig"},"KoitoClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoClientData"},"TealClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/TealClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/TealClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using Tealfm as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["tealfm"]}},"required":["data","name","type"],"title":"TealClientAIOConfig"},"TealClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealClientData"},"TealClientOptions":{"type":"object","properties":{"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealClientOptions"},"RockSkyClientAIOConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this client. Used with sources to restrict where scrobbles are sent.","examples":["MyConfig"]},"data":{"$ref":"#/definitions/RockSkyClientData","description":"Specific data required to configure this client"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"options":{"$ref":"#/definitions/RockSkyClientOptions"},"configureAs":{"type":"string","enum":["client","source"],"description":"Should always be `client` when using RockSky as a client","default":"client","examples":["client"]},"type":{"type":"string","enum":["rocksky"]}},"required":["data","name","type"],"title":"RockSkyClientAIOConfig"},"RockSkyClientData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockSkyClientData"},"RockSkyClientOptions":{"type":"object","properties":{"nowPlaying":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Configure if this Client should report Now Playing from Sources that can scrobble to it\n\n* `true` (default) => Report Now Playing from any eligible Source. \n * If multiple Sources are Playing then reported Play is based on alphabetical order of Source names\n* `false` => Do not report Now Playing\n* `string` list => list of Source `names` that should be allowed to report Now Playing. Order of list determine priority of Play to Report.","default":true},"refreshEnabled":{"type":"boolean","description":"Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history","default":true,"examples":[true]},"refreshStaleAfter":{"type":"number","description":"Refresh scrobbled plays from upstream service if last refresh was at least X seconds ago\n\n**In most case this setting does NOT need to be changed.** The default value is sufficient for the majority of use-cases. Increasing this setting may increase upstream service load and slow down scrobbles.\n\nThis setting should only be changed in specific scenarios where MS is handling multiple \"relaying\" client-services (IE lfm -> lz -> lfm) and there is the potential for a client to be out of sync after more than a few seconds.","examples":[60],"default":60},"refreshMinInterval":{"type":"number","description":"Minimum time (milliseconds) required to pass before upstream scrobbles can be refreshed.\n\n**In most case this setting does NOT need to be changed.** This will always be equal to or smaller than `refreshStaleAfter`.","default":5000,"examples":[5000]},"refreshInitialCount":{"type":"number","description":"The number of tracks to retrieve on initial refresh (related to scrobbleBacklogCount). If not specified this is the maximum supported by the client in 1 API call."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"checkExistingScrobbles":{"type":"boolean","description":"Check client for an existing scrobble at the same recorded time as the \"new\" track to be scrobbled. If an existing scrobble is found this track is not track scrobbled.","default":true,"examples":[true]},"verbose":{"type":"object","properties":{"match":{"$ref":"#/definitions/MatchLoggingOptions"}},"description":"Options used for increasing verbosity of logging in MS (used for debugging)"},"deadLetterRetries":{"type":"number","description":"Number of times MS should automatically retry scrobbles in dead letter queue","default":1,"examples":[1]},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockSkyClientOptions"},"WebhookConfig":{"anyOf":[{"$ref":"#/definitions/GotifyConfig"},{"$ref":"#/definitions/NtfyConfig"},{"$ref":"#/definitions/AppriseConfig"}],"title":"WebhookConfig"},"GotifyConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"url":{"type":"string","description":"The URL of the Gotify server. Same URL that would be used to reach the Gotify UI","examples":["http://192.168.0.100:8078"]},"token":{"type":"string","description":"The token created for this Application in Gotify","examples":["AQZI58fA.rfSZbm"]},"priorities":{"$ref":"#/definitions/PrioritiesConfig","description":"Priority of messages\n\n* Info -> 5\n* Warn -> 7\n* Error -> 10"}},"required":["token","type","url"],"title":"GotifyConfig"},"PrioritiesConfig":{"type":"object","properties":{"info":{"type":"number","examples":[5]},"warn":{"type":"number","examples":[7]},"error":{"type":"number","examples":[10]}},"required":["info","warn","error"],"title":"PrioritiesConfig"},"NtfyConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"url":{"type":"string","description":"The URL of the Ntfy server","examples":["http://192.168.0.100:8078"]},"topic":{"type":"string","description":"The topic mutli-scrobbler should POST to"},"username":{"type":"string","description":"Required if topic is protected"},"password":{"type":"string","description":"Required if topic is protected"},"token":{"type":"string","description":"Use instead of username/password, required if topic is protected"},"priorities":{"$ref":"#/definitions/PrioritiesConfig","description":"Priority of messages\n\n* Info -> 3\n* Warn -> 4\n* Error -> 5"}},"required":["topic","type","url"],"title":"NtfyConfig"},"AppriseConfig":{"type":"object","properties":{"type":{"type":"string","enum":["gotify","ntfy","apprise"],"description":"Webhook type. Valid values are:\n\n* gotify\n* ntfy","examples":["gotify"]},"name":{"type":"string","description":"A friendly name used to identify webhook config in logs"},"host":{"type":"string","description":"The URL of the apprise-api server","examples":["http://192.168.0.100:8078"]},"urls":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"If using [Stateless Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#stateless-solution) the Apprise config URL(s) to send"},"keys":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"If using [Persistent Store Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#persistent-storage-solution) the Configuration ID(s) to send to\n\nNote: If multiple keys are defined then MS will attempt to POST to each one individually"},"tags":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Optional [tag(s)](https://github.com/caronc/apprise-api?tab=readme-ov-file#tagging) to send in the notification payload"}},"required":["host","type"],"title":"AppriseConfig"},"LogOptions":{"type":"object","properties":{"level":{"$ref":"#/definitions/LogLevel","description":"Specify the minimum log level for all log outputs without their own level specified.\n\nDefaults to env `LOG_LEVEL` or `info` if not specified.","default":"info"},"file":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false},{"$ref":"#/definitions/FileLogOptions"}],"description":"Specify the minimum log level to output to rotating files or file output options. If `false` no log files will be created."},"console":{"$ref":"#/definitions/LogLevel","description":"Specify the minimum log level streamed to the console (or docker container)"}},"description":"Configure log levels and file options for an AppLogger.\n\n```ts\nconst infoLogger = loggerApp({\n level: 'info' // console and file will log any levels `info` and above\n});\n\nconst logger = loggerApp({\n console: 'debug', // console will log `debug` and higher\n file: 'warn' // file will log `warn` and higher\n});\n\nconst fileLogger = loggerRollingApp({\n console: 'debug', // console will log `debug` and higher\n file: {\n level: 'warn', // file will log `warn` and higher\n path: '/my/cool/path/output.log', // optionally, output to log file at this path\n frequency: 'hourly', // optionally, rotate hourly\n }\n});\n```","title":"LogOptions"},"CacheConfigOptions":{"type":"object","properties":{"metadata":{"$ref":"#/definitions/CacheMetadataConfig"},"scrobble":{"$ref":"#/definitions/CacheScrobbleConfig"},"auth":{"$ref":"#/definitions/CacheAuthConfig"}},"title":"CacheConfigOptions"},"CacheMetadataConfig":{"$ref":"#/definitions/CacheConfig%3CCacheMetadataProvider%3E","title":"CacheMetadataConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheMetadataProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheMetadataProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheMetadataProvider"},"CacheProvider":{"type":["string","boolean"],"enum":["memory","valkey","file",false],"title":"CacheProvider"},"CacheScrobbleConfig":{"$ref":"#/definitions/CacheConfig%3CCacheScrobbleProvider%3E","title":"CacheScrobbleConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheScrobbleProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheScrobbleProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheScrobbleProvider"},"CacheAuthConfig":{"$ref":"#/definitions/CacheConfig%3CCacheAuthProvider%3E","title":"CacheAuthConfig"},"CacheConfig":{"type":"object","properties":{"provider":{"$ref":"#/definitions/CacheAuthProvider"},"connection":{"type":"string"}},"required":["provider"],"title":"CacheConfig"},"CacheAuthProvider":{"$ref":"#/definitions/CacheProvider","title":"CacheAuthProvider"}}} diff --git a/src/backend/common/schema/source.json b/src/backend/common/schema/source.json index 26705a06..08d89836 100644 --- a/src/backend/common/schema/source.json +++ b/src/backend/common/schema/source.json @@ -1 +1 @@ -{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"$ref":"#/definitions/SpotifySourceConfig"},{"$ref":"#/definitions/PlexSourceConfig"},{"$ref":"#/definitions/PlexApiSourceConfig"},{"$ref":"#/definitions/TautulliSourceConfig"},{"$ref":"#/definitions/DeezerSourceConfig"},{"$ref":"#/definitions/DeezerInternalSourceConfig"},{"$ref":"#/definitions/ListenbrainzEndpointSourceConfig"},{"$ref":"#/definitions/LastFMEndpointSourceConfig"},{"$ref":"#/definitions/SubSonicSourceConfig"},{"$ref":"#/definitions/JellySourceConfig"},{"$ref":"#/definitions/JellyApiSourceConfig"},{"$ref":"#/definitions/LastfmSourceConfig"},{"$ref":"#/definitions/YTMusicSourceConfig"},{"$ref":"#/definitions/MPRISSourceConfig"},{"$ref":"#/definitions/MopidySourceConfig"},{"$ref":"#/definitions/ListenBrainzSourceConfig"},{"$ref":"#/definitions/JRiverSourceConfig"},{"$ref":"#/definitions/KodiSourceConfig"},{"$ref":"#/definitions/WebScrobblerSourceConfig"},{"$ref":"#/definitions/ChromecastSourceConfig"},{"$ref":"#/definitions/MalojaSourceConfig"},{"$ref":"#/definitions/MusikcubeSourceConfig"},{"$ref":"#/definitions/MusicCastSourceConfig"},{"$ref":"#/definitions/MPDSourceConfig"},{"$ref":"#/definitions/VLCSourceConfig"},{"$ref":"#/definitions/IcecastSourceConfig"},{"$ref":"#/definitions/AzuracastSourceConfig"},{"$ref":"#/definitions/KoitoSourceConfig"},{"$ref":"#/definitions/TealSourceConfig"},{"$ref":"#/definitions/RockskySourceConfig"}],"definitions":{"SpotifySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SpotifySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"SpotifySourceConfig"},"SpotifySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)\n\nIt is unlikely you should need to change this unless you scrobble many very short tracks often\n\nReading:\n* https://developer.spotify.com/documentation/web-api/guides/rate-limits/\n* https://medium.com/mendix/limiting-your-amount-of-calls-in-mendix-most-of-the-time-rest-835dde55b10e\n * Rate limit may ~180 req/min\n* https://community.spotify.com/t5/Spotify-for-Developers/Web-API-ratelimit/m-p/5503150/highlight/true#M7930\n * Informally indicated as 20 req/sec? Probably for burstiness","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"spotify client id","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"clientSecret":{"type":"string","description":"spotify client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"spotify redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/callback","examples":["http://localhost:9078/callback"]}},"required":["clientId","clientSecret"],"title":"SpotifySourceData"},"CommonSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonSourceOptions"},"LogLevel":{"type":"string","enum":["silent","fatal","error","warn","info","log","verbose","debug"],"description":"Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.","title":"LogLevel"},"FileLogOptions":{"type":"object","properties":{"timestamp":{"type":"string","enum":["unix","iso","auto"],"description":"For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime","default":"auto"},"size":{"type":["number","string"],"description":"The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.","default":"10MB"},"frequency":{"anyOf":[{"type":"string","enum":["daily"]},{"type":"string","enum":["hourly"]},{"type":"number"}],"description":"The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.","default":"daily"},"path":{"anyOf":[{"type":"string"},{"$comment":"() => string"}],"description":"The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD","default":"./logs/app.log"},"level":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false}],"description":"Specify the minimum log level to output to rotating files. If `false` no log files will be created."}},"title":"FileLogOptions"},"ScrobbleThresholds":{"type":"object","properties":{"duration":{"type":["number","null"],"description":"The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.","default":240,"examples":[240]},"percent":{"type":["number","null"],"description":"The percentage (as an integer) of a track that should have been seen played before it should be scrobbled. Only used if the Source provides information about how long the track is.\n\nSet to null to disable.\n\nNOTE: This should be used with care when the Source is a \"polling\" type (has an 'interval' property). If the track is short and the interval is too high MS may ignore the track if percentage is high because it had not \"seen\" the track for long enough from first discovery, even if you have been playing the track for longer.","default":50,"examples":[50]}},"title":"ScrobbleThresholds"},"PlayTransformOptions":{"type":"object","properties":{"log":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["all"]}]},"preCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"compare":{"type":"object","properties":{"candidate":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"existing":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}}},"postCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}},"title":"PlayTransformOptions"},"PlayTransformPartsConfig":{"anyOf":[{"$ref":"#/definitions/PlayTransformPartsArray%3CSearchAndReplaceTerm%3E"},{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"}],"title":"PlayTransformPartsConfig"},"PlayTransformPartsArray":{"type":"array","items":{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"},"title":"PlayTransformPartsArray"},"PlayTransformParts":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"title":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"artists":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"album":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}}},"title":"PlayTransformParts"},"WhenConditionsConfig":{"$ref":"#/definitions/WhenConditions%3Cstring%3E","title":"WhenConditionsConfig"},"WhenConditions":{"type":"array","items":{"$ref":"#/definitions/WhenParts%3Cstring%3E"},"title":"WhenConditions"},"WhenParts":{"$ref":"#/definitions/PlayTransformPartsAtomic%3Cstring%3E","title":"WhenParts"},"PlayTransformPartsAtomic":{"type":"object","properties":{"title":{"type":"string"},"artists":{"type":"string"},"album":{"type":"string"}},"title":"PlayTransformPartsAtomic"},"SearchAndReplaceTerm":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/ConditionalSearchAndReplaceTerm"}],"title":"SearchAndReplaceTerm"},"ConditionalSearchAndReplaceTerm":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"search":{},"replace":{}},"required":["search","replace"],"title":"ConditionalSearchAndReplaceTerm"},"PlexSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"PlexSourceConfig"},"PlexSourceData":{"type":"object","properties":{"user":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"libraries":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of libraries to scrobble tracks from\n\nIf none are provided tracks from all libraries will be scrobbled","examples":[["Audio","Music"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName"]]}},"title":"PlexSourceData"},"PlexApiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/PlexApiOptions"}},"required":["data","options"],"title":"PlexApiSourceConfig"},"PlexApiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"token":{"type":"string"},"url":{"type":"string","description":"http(s)://HOST:PORT of the Plex server to connect to"},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"}},"required":["url"],"title":"PlexApiData"},"PlexApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"ignoreInvalidCert":{"type":"boolean","description":"Ignore invalid cert errors when connecting to Plex\n\nUseful for Plex servers using \"Required\" Secure Connections with self-signed certificates\n\nDo not enable unless you know you need this.","default":false}},"title":"PlexApiOptions"},"TautulliSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"TautulliSourceConfig"},"DeezerSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"DeezerSourceConfig"},"DeezerData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"deezer client id","examples":["a89cba1569901a0671d5a9875fed4be1"]},"clientSecret":{"type":"string","description":"deezer client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/deezer/callback","examples":["http://localhost:9078/deezer/callback"]}},"required":["clientId","clientSecret"],"title":"DeezerData"},"DeezerInternalSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerInternalData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/DeezerInternalSourceOptions"}},"required":["data"],"title":"DeezerInternalSourceConfig"},"DeezerInternalData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"arl":{"type":"string","description":"ARL retrieved from Deezer response header"},"userAgent":{"type":"string","description":"User agent","default":"Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0"}},"required":["arl"],"title":"DeezerInternalData"},"DeezerInternalSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"fuzzyDiscoveryIgnore":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["aggressive"]}]}},"title":"DeezerInternalSourceOptions"},"ListenbrainzEndpointSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenbrainzEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"ListenbrainzEndpointSourceConfig"},"ListenbrainzEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Listenbrainz endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/listenbrainz/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/listenbrainz/originb\n\nIf no slug is found from an extension's incoming webhook event the first Listenbrainz source without a slug will be used"},"token":{"type":["string","null"],"description":"If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source\n\nSee: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#add-the-user-token-to-your-requests"}},"title":"ListenbrainzEndpointData"},"LastFMEndpointSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFMEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"LastFMEndpointSourceConfig"},"LastFMEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Last.fm endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/lastfm/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/lastfm/originb\n\nIf no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used"}},"title":"LastFMEndpointData"},"SubSonicSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SubsonicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"SubSonicSourceConfig"},"SubsonicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the subsonic media server to query","examples":["http://airsonic.local"]},"user":{"type":"string","description":"Username to login to the server with","examples":[["MyUser"]]},"password":{"type":"string","description":"Password for the user to login to the server with","examples":["MyPassword"]},"ignoreTlsErrors":{"type":"boolean","description":"If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection\n\nWARNING: This should be used with caution as your traffic may not be encrypted.","default":false},"legacyAuthentication":{"type":"boolean","description":"Older Subsonic versions, and some badly implemented servers (Nextcloud), use legacy authentication which sends your password in CLEAR TEXT. This is less secure than the newer, recommended hashing authentication method but in some cases it is needed. See \"Authentication\" section here => https://www.subsonic.org/pages/api.jsp\n\nIf this option is not specified it will be turned on if the subsonic server responds with error code 41 \"Token authentication not supported for LDAP users.\" -- See Error Handling section => https://www.subsonic.org/pages/api.jsp","default":false},"usersAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf undefined or an empty string/list MS will scrobble activity from all users"}},"required":["url","user","password"],"title":"SubsonicData"},"JellySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"JellySourceConfig"},"JellyData":{"type":"object","properties":{"users":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName1"]]}},"title":"JellyData"},"JellyApiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/JellyApiOptions"}},"required":["data","options"],"title":"JellyApiSourceConfig"},"JellyApiData":{"type":"object","properties":{"url":{"type":"string","description":"HOST:PORT of the Jellyfin server to connect to"},"user":{"type":"string","description":"The username of the user to authenticate for or track scrobbles for"},"password":{"type":"string","description":"Password of the username to authenticate for\n\nRequired if `apiKey` is not provided."},"apiKey":{"type":"string","description":"API Key to authenticate with.\n\nRequired if `password` is not provided."},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"},"additionalAllowedLibraryTypes":{"type":"array","items":{},"description":"Allow MS to scrobble audio media in libraries classified other than 'music'\n\n`librariesAllow` will achieve the same result as this but this is more convenient if you do not want to explicitly list every library name or are only using `librariesBlock`"},"allowUnknown":{"type":"boolean","description":"Force media with a type of \"Unknown\" to be counted as Audio","default":false},"frontendUrlOverride":{"type":"string","description":"HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from)\nIf unspecified it will use the normal server HOST and PORT from the `url`\nNecessary if you are using a reverse proxy or other network configuration that prevents the frontend from accessing the server directly\n\nENV: JELLYFIN_FRONTEND_URL_OVERRIDE"}},"required":["url","user"],"title":"JellyApiData"},"JellyApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"JellyApiOptions"},"LastfmSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFmSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"LastfmSourceConfig"},"LastFmSourceData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]},"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"}},"required":["apiKey","secret"],"title":"LastFmSourceData"},"YTMusicSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/YTMusicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"type":"object","properties":{"logAuth":{"type":"boolean","description":"When true MS will log to DEBUG all of the credentials data it receives from YTM"},"logDiff":{"type":"boolean","description":"Always log history diff\n\nBy default MS will log to `WARN` if history diff is inconsistent but does not log if diff is expected (on new tracks found)\nSet this to `true` to ALWAYS log diff on new tracks. Expected diffs will log to `DEBUG` and inconsistent diffs will continue to log to `WARN`","default":false},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}}}},"title":"YTMusicSourceConfig"},"YTMusicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"cookie":{"type":"string","description":"The cookie retrieved from the Request Headers of music.youtube.com after logging in.\n\nSee https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers for how to retrieve this value.","examples":["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]},"clientId":{"type":"string","description":"Google Cloud Console project OAuth Client ID\n\nGenerated from a custom OAuth Client, see docs"},"clientSecret":{"type":"string","description":"Google Cloud Console project OAuth Client Secret\n\nGenerated from a custom OAuth Client, see docs"},"redirectUri":{"type":"string","description":"Google Cloud Console project OAuth Client Authorized redirect URI\n\nGenerated from a custom OAuth Client, see docs. multi-scrobbler will generate a default based on BASE_URL.\nOnly specify this if the default does not work for you."},"innertubeOptions":{"$ref":"#/definitions/InnertubeOptions","description":"Additional options for authorization and tailoring YTM client"}},"title":"YTMusicData"},"InnertubeOptions":{"type":"object","properties":{"po_token":{"type":"string","description":"Proof of Origin token\n\nMay be required if YTM starts returning 403"},"visitor_data":{"type":"string","description":"Visitor ID value found in VISITOR_INFO1_LIVE or visitorData cookie\n\nMay be required if YTM starts returning 403"},"account_index":{"type":"number","description":"If account login results in being able to choose multiple account, use a zero-based index to choose which one to monitor","examples":[0,1]},"location":{"type":"string"},"lang":{"type":"string"},"generate_session_locally":{"type":"boolean"},"device_category":{"type":"string"},"client_type":{"type":"string"},"timezone":{"type":"string"}},"title":"InnertubeOptions"},"MPRISSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPRISData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MPRISSourceConfig"},"MPRISData":{"type":"object","properties":{"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any players that START WITH these values, case-insensitive","examples":[["spotify","vlc"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","vlc"]]}},"title":"MPRISData"},"MopidySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MopidyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MopidySourceConfig"},"MopidyData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Mopidy HTTP server to connect to\n\nYou MUST have Mopidy-HTTP extension enabled: https://mopidy.com/ext/http\n\nmulti-scrobbler connects to the WebSocket endpoint that ultimately looks like this => `ws://localhost:6680/mopidy/ws/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `6680`\n* Path => `/mopidy/ws/`","examples":["ws://localhost:6680/mopidy/ws/"],"default":"ws://localhost:6680/mopidy/ws/"},"uriBlacklist":{"type":"array","items":{"type":"string"},"description":"Do not scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Don't scrobble tracks from soundcloud by adding 'soundcloud' to this list.\n\nList is ignored if uriWhitelist is used."},"uriWhitelist":{"type":"array","items":{"type":"string"},"description":"Only scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Only scrobble tracks from soundcloud by adding 'soundcloud' to this list."},"albumBlacklist":{"type":"array","items":{"type":"string"},"description":"Remove album data that matches any case-insensitive string from this list when scrobbling,\n\nFor certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use \"Soundcloud\" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake name to this list. Multi-scrobbler will still scrobble the track, just without the bad data.","examples":[["Soundcloud","Mixcloud"]],"default":["Soundcloud"]}},"title":"MopidyData"},"ListenBrainzSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenBrainzSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `listenbrainz.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"ListenBrainzSourceConfig"},"ListenBrainzSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzSourceData"},"JRiverSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JRiverData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"JRiverSourceConfig"},"JRiverData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the JRiver HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:52199/MCWS/v1/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `52199`\n* Path => `/MCWS/v1/`","examples":["http://localhost:52199/MCWS/v1/"],"default":"http://localhost:52199/MCWS/v1/"},"username":{"type":"string","description":"If you have enabled authentication, the username you set"},"password":{"type":"string","description":"If you have enabled authentication, the password you set"}},"required":["url"],"title":"JRiverData"},"KodiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KodiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"KodiSourceConfig"},"KodiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Kodi HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:8080/jsonrpc`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `8080`\n* Path => `/jsonrpc`","examples":["http://localhost:8080/jsonrpc"],"default":"http://localhost:8080/jsonrpc"},"username":{"type":"string","description":"The username set for Remote Control via Web Sever"},"password":{"type":"string","description":"The password set for Remote Control via Web Sever"}},"required":["url","username","password"],"title":"KodiData"},"WebScrobblerSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/WebScrobblerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"WebScrobblerSourceConfig"},"WebScrobblerData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIn WebScrobbler's Webhook you must set an 'API URL'. All MS WebScrobbler sources must start like:\n\nhttp://localhost:9078/api/webscrobbler\n\nIf you are using multiple WebScrobbler sources (scrobbles for many users) you must use a slug to match Sources with each users extension.\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/webscrobbler/usera\n* slug: 'userb' => API URL: http://localhost:9078/api/webscrobbler/userb\n\nIf no slug is found from an extension's incoming webhook event the first WebScrobbler source without a slug will be used"},"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Block scrobbling from specific WebScrobbler Connectors","examples":[["youtube"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only allow scrobbling from specific WebScrobbler Connectors","examples":[["mixcloud","soundcloud","bandcamp"]]}},"title":"WebScrobblerData"},"ChromecastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ChromecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"ChromecastSourceConfig"},"ChromecastData":{"type":"object","properties":{"blacklistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"whitelistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any cast device that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"blacklistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any application that START WITH these values, case-insensitive","examples":[["spotify","pandora"]]},"whitelistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any application that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","pandora"]]},"useAvahi":{"type":"boolean","description":"Try to use Avahi and avahi-browse to resolve mDNS devices instead of native mDNS querying\n\nUseful for docker (alpine) container where mDNS resolution is not yet supported. Avahi socket must be exposed to the container and avahi-tools must be installed.","default":false},"useAutoDiscovery":{"type":"boolean","description":"Use mDNS to discovery Google Cast devices on your next automatically?\n\nIf not explicitly set then it is TRUE if `devices` is not set"},"devices":{"type":"array","items":{"$ref":"#/definitions/ChromecastDeviceInfo"},"description":"A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set"},"allowUnknownMedia":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Chromecast Apps report a \"media type\" in the status info returned for whatever is currently playing\n\n* If set to TRUE then Music AND Generic/Unknown media will be tracked for ALL APPS\n* If set to FALSE then only media explicitly typed as Music will be tracked for ALL APPS\n* If set to a list then only Apps whose name contain one of these values, case-insensitive, will have Music AND Generic/Unknown tracked\n\nSee https://developers.google.com/cast/docs/media/messages#MediaInformation \"metadata\" property","default":false},"forceMediaRecognitionOn":{"type":"array","items":{"type":"string"},"description":"Media provided by any App whose name is listed here will ALWAYS be tracked, regardless of the \"media type\" reported\n\nApps will be recognized if they CONTAIN any of these values, case-insensitive"}},"title":"ChromecastData"},"ChromecastDeviceInfo":{"type":"object","properties":{"name":{"type":"string","description":"A friendly name to identify this device","examples":["MySmartTV"]},"address":{"type":"string","description":"The IP address of the device","examples":["192.168.0.115"]}},"required":["name","address"],"title":"ChromecastDeviceInfo"},"MalojaSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MalojaSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `maloja.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"MalojaSourceConfig"},"MalojaSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaSourceData"},"MusikcubeSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusikcubeData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MusikcubeSourceConfig"},"MusikcubeData":{"type":"object","properties":{"url":{"type":"string","description":"URL of the Musikcube Websocket (Metadata) server to connect to\n\nYou MUST have enabled 'metadata' server and set a password: https://github.com/clangen/musikcube/wiki/remote-api-documentation\n * musikcube -> settings -> server setup\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `7905`","examples":["ws://localhost:7905"],"default":"ws://localhost:7905"},"password":{"type":"string","description":"Password set in Musikcube https://github.com/clangen/musikcube/wiki/remote-api-documentation\n\n* musikcube -> settings -> server setup -> password"},"device_id":{"type":"string"}},"required":["password"],"title":"MusikcubeData"},"MusicCastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusicCastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MusicCastSourceConfig"},"MusicCastData":{"type":"object","properties":{"url":{"type":"string","description":"The host or URL of the YamahaExtendedControl endpoint to use","examples":[["192.168.0.101","http://192.168.0.101/YamahaExtendedControl"]]}},"required":["url"],"title":"MusicCastData"},"MPDSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPDData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/MPDSourceOptions"}},"required":["data","options"],"title":"MPDSourceConfig"},"MPDData":{"type":"object","properties":{"url":{"type":"string","description":"URL:PORT of the MPD server to connect to\n\nTo use this you must have TCP connections enabled for your MPD server https://mpd.readthedocs.io/en/stable/user.html#client-connections","examples":["localhost:6600"],"default":"localhost:6600"},"path":{"type":"string","description":"If using socket specify the path instead of url.\n\ntrailing `~` is replaced by your home directory"},"password":{"type":"string","description":"Password for the server, if set https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords"}},"title":"MPDData"},"MPDSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"MPDSourceOptions"},"VLCSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/VLCData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/VLCSourceOptions"}},"required":["data"],"title":"VLCSourceConfig"},"VLCData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL:PORT of the VLC server to connect to\n\nTo use this you must have the Web (http) interface module enabled and a password set https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc","examples":["localhost:8080"],"default":"localhost:8080"},"password":{"type":"string","description":"Password for the server"}},"required":["password"],"title":"VLCData"},"VLCSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"filenamePatterns":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A list of regular expressions to use to extract metadata (title, album, artist) from a filename\n\nUsed when VLC reports only the filename for the current audio track"},"logFilenamePatterns":{"type":"boolean","description":"Log to DEBUG when a filename-only track is matched or not matched by filenamePatterns","default":false},"dumpVlcMetadata":{"type":"boolean","description":"Dump all the metadata VLC reports for an audio track to DEBUG.\n\nUse this if reporting an issue with VLC not correctly capturing metadata for a track.","default":false}},"title":"VLCSourceOptions"},"IcecastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/IcecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/IcecastSourceOptions"}},"required":["data"],"title":"IcecastSourceConfig"},"IcecastData":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/definitions/IcecastSource"}},"icestatsEndpoint":{"type":"string"},"statsEndpoint":{"type":"string"},"nextsongsEndpoint":{"type":"string"},"sevenhtmlEndpoint":{"type":"string"},"icyMetaInt":{"type":"number"},"url":{"type":"string","description":"The Icecast stream URL"}},"required":["url"],"title":"IcecastData"},"IcecastSource":{"type":"string","enum":["icy","ogg","icestats","stats","sevenhtml","nextsongs"],"title":"IcecastSource"},"IcecastSourceOptions":{"type":"object","properties":{"systemScrobble":{"type":"boolean","description":"For Sources that support manual listening, should MS default to scrobbling when no user interaction has occurred?\n\nIf not specified MS will use a Source's specific behavior, see Source's documentation."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"IcecastSourceOptions"},"AzuracastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/AzuracastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"AzuracastSourceConfig"},"AzuracastData":{"type":"object","properties":{"url":{"type":"string","description":"Base URL of the Azuracast instance\n\nThis does NOT include the station. If a station is included it will be ignored. Use `station` field to specify station, if necessary","examples":["https://radio.mydomain.tld","http://localhost:80"]},"station":{"type":"string","description":"The specific station to monitor\n\nScrobbling will only occur if any of the monitor conditions are met AND the station is ONLINE.\n\nTo monitor multiple stations create a Source for each station.","examples":["my-station-1"]},"monitorWhenListeners":{"type":["boolean","number"],"description":"Only activate scrobble monitoring if station\n\n* `true` => has any current listeners\n* `number` => has EQUAL TO or MORE THAN X number of listeners"},"monitorWhenLive":{"type":"boolean","description":"Only activate scrobble monitoring if station has a live DJ/Streamer","default":true},"apiKey":{"type":"string","description":"API Key used to access data about private streams\n\nhttps://www.azuracast.com/docs/developers/apis/#api-authentication"}},"required":["url","station"],"title":"AzuracastData"},"KoitoSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KoitoSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `koito.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"KoitoSourceConfig"},"KoitoSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoSourceData"},"TealSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/TealSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/TealSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"Should always be `souce` when using Tealfm as a Source","default":"source","examples":["source"]}},"required":["data"],"title":"TealSourceConfig"},"TealSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"baseUri":{"type":"string","description":"The base URI of the Multi-Scrobbler to use for ATProto OAuth\n\nOnly include this if you want to use OAuth. The URI must be a non-IP/non-local domain using https: protocol."},"identifier":{"type":"string","description":"Identify the account to login as\n\n* For **App Password** Auth - your email\n* For **Oauth** - your handle minus the @"},"appPassword":{"type":"string","description":"The [App Password](https://atproto.com/specs/xrpc#app-passwords) you created for your account\n\nThis is created under https://bsky.app/settings/app-passwords\n\n**Use this if you are self-hosting Multi-Scrobbler on localhost or accessed like http://IP:PORT**"}},"required":["identifier"],"title":"TealSourceData"},"TealSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"TealSourceOptions"},"RockskySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/RockskySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/RockskySourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `rocksky.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"RockskySourceConfig"},"RockskySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"key":{"type":"string","description":"API Key generated from [API Applications](https://docs.rocksky.app/migrating-from-listenbrainz-to-rocksky-1040189m0) in Rocksky for your account","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"handle":{"type":"string","description":"The **fully-qualified** handle for your ATPRoto/Bluesky account, like:\n\n* alice.bsky.social\n* foxxmd.com\n* mysuer.blacksky.app"}},"required":["handle","key"],"title":"RockskySourceData"},"RockskySourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"audioScrobblerUrl":{"type":"string","description":"URL for the Rocksky *Listenbrainz* endpoint, if not using the default","examples":["https://audioscrobbler.rocksky.app"],"default":"https://audioscrobbler.rocksky.app"},"apiUrl":{"type":"string","description":"URL for the Rocksky *API* endpoint, if not using the default","examples":["https://api.rocksky.app"],"default":"https://api.rocksky.app"}},"title":"RockskySourceOptions"}}} \ No newline at end of file +{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"$ref":"#/definitions/SpotifySourceConfig"},{"$ref":"#/definitions/PlexSourceConfig"},{"$ref":"#/definitions/PlexApiSourceConfig"},{"$ref":"#/definitions/TautulliSourceConfig"},{"$ref":"#/definitions/DeezerSourceConfig"},{"$ref":"#/definitions/DeezerInternalSourceConfig"},{"$ref":"#/definitions/ListenbrainzEndpointSourceConfig"},{"$ref":"#/definitions/LastFMEndpointSourceConfig"},{"$ref":"#/definitions/SubSonicSourceConfig"},{"$ref":"#/definitions/JellySourceConfig"},{"$ref":"#/definitions/JellyApiSourceConfig"},{"$ref":"#/definitions/LastfmSourceConfig"},{"$ref":"#/definitions/YTMusicSourceConfig"},{"$ref":"#/definitions/MPRISSourceConfig"},{"$ref":"#/definitions/MopidySourceConfig"},{"$ref":"#/definitions/ListenBrainzSourceConfig"},{"$ref":"#/definitions/JRiverSourceConfig"},{"$ref":"#/definitions/KodiSourceConfig"},{"$ref":"#/definitions/WebScrobblerSourceConfig"},{"$ref":"#/definitions/ChromecastSourceConfig"},{"$ref":"#/definitions/MalojaSourceConfig"},{"$ref":"#/definitions/MusikcubeSourceConfig"},{"$ref":"#/definitions/MusicCastSourceConfig"},{"$ref":"#/definitions/MPDSourceConfig"},{"$ref":"#/definitions/VLCSourceConfig"},{"$ref":"#/definitions/IcecastSourceConfig"},{"$ref":"#/definitions/AzuracastSourceConfig"},{"$ref":"#/definitions/KoitoSourceConfig"}],"definitions":{"SpotifySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SpotifySourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"SpotifySourceConfig"},"SpotifySourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)\n\nIt is unlikely you should need to change this unless you scrobble many very short tracks often\n\nReading:\n* https://developer.spotify.com/documentation/web-api/guides/rate-limits/\n* https://medium.com/mendix/limiting-your-amount-of-calls-in-mendix-most-of-the-time-rest-835dde55b10e\n * Rate limit may ~180 req/min\n* https://community.spotify.com/t5/Spotify-for-Developers/Web-API-ratelimit/m-p/5503150/highlight/true#M7930\n * Informally indicated as 20 req/sec? Probably for burstiness","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"spotify client id","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"clientSecret":{"type":"string","description":"spotify client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"spotify redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/callback","examples":["http://localhost:9078/callback"]}},"required":["clientId","clientSecret"],"title":"SpotifySourceData"},"CommonSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"CommonSourceOptions"},"LogLevel":{"type":"string","enum":["silent","fatal","error","warn","info","log","verbose","debug"],"description":"Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.","title":"LogLevel"},"FileLogOptions":{"type":"object","properties":{"timestamp":{"type":"string","enum":["unix","iso","auto"],"description":"For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime","default":"auto"},"size":{"type":["number","string"],"description":"The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.","default":"10MB"},"frequency":{"anyOf":[{"type":"string","enum":["daily"]},{"type":"string","enum":["hourly"]},{"type":"number"}],"description":"The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.","default":"daily"},"path":{"anyOf":[{"type":"string"},{"$comment":"() => string"}],"description":"The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD","default":"./logs/app.log"},"level":{"anyOf":[{"$ref":"#/definitions/LogLevel"},{"type":"boolean","const":false}],"description":"Specify the minimum log level to output to rotating files. If `false` no log files will be created."}},"title":"FileLogOptions"},"ScrobbleThresholds":{"type":"object","properties":{"duration":{"type":["number","null"],"description":"The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.","default":240,"examples":[240]},"percent":{"type":["number","null"],"description":"The percentage (as an integer) of a track that should have been seen played before it should be scrobbled. Only used if the Source provides information about how long the track is.\n\nSet to null to disable.\n\nNOTE: This should be used with care when the Source is a \"polling\" type (has an 'interval' property). If the track is short and the interval is too high MS may ignore the track if percentage is high because it had not \"seen\" the track for long enough from first discovery, even if you have been playing the track for longer.","default":50,"examples":[50]}},"title":"ScrobbleThresholds"},"PlayTransformOptions":{"type":"object","properties":{"log":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["all"]}]},"preCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"compare":{"type":"object","properties":{"candidate":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"},"existing":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}}},"postCompare":{"$ref":"#/definitions/PlayTransformPartsConfig%3CSearchAndReplaceTerm%3E"}},"title":"PlayTransformOptions"},"PlayTransformPartsConfig":{"anyOf":[{"$ref":"#/definitions/PlayTransformPartsArray%3CSearchAndReplaceTerm%3E"},{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"}],"title":"PlayTransformPartsConfig"},"PlayTransformPartsArray":{"type":"array","items":{"$ref":"#/definitions/PlayTransformParts%3CSearchAndReplaceTerm%3E"},"title":"PlayTransformPartsArray"},"PlayTransformParts":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"title":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"artists":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}},"album":{"type":"array","items":{"$ref":"#/definitions/SearchAndReplaceTerm"}}},"title":"PlayTransformParts"},"WhenConditionsConfig":{"$ref":"#/definitions/WhenConditions%3Cstring%3E","title":"WhenConditionsConfig"},"WhenConditions":{"type":"array","items":{"$ref":"#/definitions/WhenParts%3Cstring%3E"},"title":"WhenConditions"},"WhenParts":{"$ref":"#/definitions/PlayTransformPartsAtomic%3Cstring%3E","title":"WhenParts"},"PlayTransformPartsAtomic":{"type":"object","properties":{"title":{"type":"string"},"artists":{"type":"string"},"album":{"type":"string"}},"title":"PlayTransformPartsAtomic"},"SearchAndReplaceTerm":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/ConditionalSearchAndReplaceTerm"}],"title":"SearchAndReplaceTerm"},"ConditionalSearchAndReplaceTerm":{"type":"object","properties":{"when":{"$ref":"#/definitions/WhenConditionsConfig"},"search":{},"replace":{}},"required":["search","replace"],"title":"ConditionalSearchAndReplaceTerm"},"PlexSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"PlexSourceConfig"},"PlexSourceData":{"type":"object","properties":{"user":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"libraries":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of libraries to scrobble tracks from\n\nIf none are provided tracks from all libraries will be scrobbled","examples":[["Audio","Music"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName"]]}},"title":"PlexSourceData"},"PlexApiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/PlexApiOptions"}},"required":["data","options"],"title":"PlexApiSourceConfig"},"PlexApiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"token":{"type":"string"},"url":{"type":"string","description":"http(s)://HOST:PORT of the Plex server to connect to"},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"}},"required":["url"],"title":"PlexApiData"},"PlexApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"ignoreInvalidCert":{"type":"boolean","description":"Ignore invalid cert errors when connecting to Plex\n\nUseful for Plex servers using \"Required\" Secure Connections with self-signed certificates\n\nDo not enable unless you know you need this.","default":false}},"title":"PlexApiOptions"},"TautulliSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/PlexSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"TautulliSourceConfig"},"DeezerSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"DeezerSourceConfig"},"DeezerData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"clientId":{"type":"string","description":"deezer client id","examples":["a89cba1569901a0671d5a9875fed4be1"]},"clientSecret":{"type":"string","description":"deezer client secret","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"redirectUri":{"type":"string","description":"deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"","default":"http://localhost:9078/deezer/callback","examples":["http://localhost:9078/deezer/callback"]}},"required":["clientId","clientSecret"],"title":"DeezerData"},"DeezerInternalSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/DeezerInternalData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/DeezerInternalSourceOptions"}},"required":["data"],"title":"DeezerInternalSourceConfig"},"DeezerInternalData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"arl":{"type":"string","description":"ARL retrieved from Deezer response header"},"userAgent":{"type":"string","description":"User agent","default":"Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0"}},"required":["arl"],"title":"DeezerInternalData"},"DeezerInternalSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"fuzzyDiscoveryIgnore":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["aggressive"]}]}},"title":"DeezerInternalSourceOptions"},"ListenbrainzEndpointSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenbrainzEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"ListenbrainzEndpointSourceConfig"},"ListenbrainzEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Listenbrainz endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/listenbrainz/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/listenbrainz/originb\n\nIf no slug is found from an extension's incoming webhook event the first Listenbrainz source without a slug will be used"},"token":{"type":["string","null"],"description":"If an LZ submission request contains this token in the Authorization Header it will be used to match the submission with this Source\n\nSee: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#add-the-user-token-to-your-requests"}},"title":"ListenbrainzEndpointData"},"LastFMEndpointSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFMEndpointData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"LastFMEndpointSourceConfig"},"LastFMEndpointData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIf you are using multiple Last.fm endpoint sources (scrobbles for many users) you can use a slug to match Sources with individual users/origins\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/lastfm/usera\n* slug: 'originb' => API URL: http://localhost:9078/api/lastfm/originb\n\nIf no slug is found from an extension's incoming webhook event the first Last.fm source without a slug will be used"}},"title":"LastFMEndpointData"},"SubSonicSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/SubsonicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"SubSonicSourceConfig"},"SubsonicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the subsonic media server to query","examples":["http://airsonic.local"]},"user":{"type":"string","description":"Username to login to the server with","examples":[["MyUser"]]},"password":{"type":"string","description":"Password for the user to login to the server with","examples":["MyPassword"]},"ignoreTlsErrors":{"type":"boolean","description":"If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection\n\nWARNING: This should be used with caution as your traffic may not be encrypted.","default":false},"legacyAuthentication":{"type":"boolean","description":"Older Subsonic versions, and some badly implemented servers (Nextcloud), use legacy authentication which sends your password in CLEAR TEXT. This is less secure than the newer, recommended hashing authentication method but in some cases it is needed. See \"Authentication\" section here => https://www.subsonic.org/pages/api.jsp\n\nIf this option is not specified it will be turned on if the subsonic server responds with error code 41 \"Token authentication not supported for LDAP users.\" -- See Error Handling section => https://www.subsonic.org/pages/api.jsp","default":false},"usersAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf undefined or an empty string/list MS will scrobble activity from all users"}},"required":["url","user","password"],"title":"SubsonicData"},"JellySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"JellySourceConfig"},"JellyData":{"type":"object","properties":{"users":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled","examples":[["MyUser1","MyUser2"]]},"servers":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled","examples":[["MyServerName1"]]}},"title":"JellyData"},"JellyApiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JellyApiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/JellyApiOptions"}},"required":["data","options"],"title":"JellyApiSourceConfig"},"JellyApiData":{"type":"object","properties":{"url":{"type":"string","description":"HOST:PORT of the Jellyfin server to connect to"},"user":{"type":"string","description":"The username of the user to authenticate for or track scrobbles for"},"password":{"type":"string","description":"Password of the username to authenticate for\n\nRequired if `apiKey` is not provided."},"apiKey":{"type":"string","description":"API Key to authenticate with.\n\nRequired if `password` is not provided."},"usersAllow":{"anyOf":[{"type":"string"},{"type":"boolean","enum":[true]},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble for specific users (case-insensitive)\n\nIf `true` MS will scrobble activity from all users"},"usersBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble for these users (case-insensitive)"},"devicesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if device or application name contains strings from this list (case-insensitive)"},"devicesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if device or application name contains strings from this list (case-insensitive)"},"librariesAllow":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only scrobble if library name contains string from this list (case-insensitive)"},"librariesBlock":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Do not scrobble if library name contains strings from this list (case-insensitive)"},"additionalAllowedLibraryTypes":{"type":"array","items":{},"description":"Allow MS to scrobble audio media in libraries classified other than 'music'\n\n`librariesAllow` will achieve the same result as this but this is more convenient if you do not want to explicitly list every library name or are only using `librariesBlock`"},"allowUnknown":{"type":"boolean","description":"Force media with a type of \"Unknown\" to be counted as Audio","default":false},"frontendUrlOverride":{"type":"string","description":"HOST:PORT of the Jellyfin server that your browser will be able to access from the frontend (and thus load images and links from)\nIf unspecified it will use the normal server HOST and PORT from the `url`\nNecessary if you are using a reverse proxy or other network configuration that prevents the frontend from accessing the server directly"}},"required":["url","user"],"title":"JellyApiData"},"JellyApiOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"JellyApiOptions"},"LastfmSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/LastFmSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"LastfmSourceConfig"},"LastFmSourceData":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"apiKey":{"type":"string","description":"API Key generated from Last.fm account","examples":["787c921a2a2ab42320831aba0c8f2fc2"]},"secret":{"type":"string","description":"Secret generated from Last.fm account","examples":["ec42e09d5ae0ee0f0816ca151008412a"]},"session":{"type":"string","description":"Optional session id returned from a completed auth flow"},"redirectUri":{"type":"string","description":"Optional URI to use for callback. Specify this if callback should be different than the default. MUST have \"lastfm/callback\" in the URL somewhere.","default":"http://localhost:9078/lastfm/callback","examples":["http://localhost:9078/lastfm/callback"]},"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"}},"required":["apiKey","secret"],"title":"LastFmSourceData"},"YTMusicSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/YTMusicData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"type":"object","properties":{"logAuth":{"type":"boolean","description":"When true MS will log to DEBUG all of the credentials data it receives from YTM"},"logDiff":{"type":"boolean","description":"Always log history diff\n\nBy default MS will log to `WARN` if history diff is inconsistent but does not log if diff is expected (on new tracks found)\nSet this to `true` to ALWAYS log diff on new tracks. Expected diffs will log to `DEBUG` and inconsistent diffs will continue to log to `WARN`","default":false},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}}}},"title":"YTMusicSourceConfig"},"YTMusicData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"cookie":{"type":"string","description":"The cookie retrieved from the Request Headers of music.youtube.com after logging in.\n\nSee https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers for how to retrieve this value.","examples":["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]},"clientId":{"type":"string","description":"Google Cloud Console project OAuth Client ID\n\nGenerated from a custom OAuth Client, see docs"},"clientSecret":{"type":"string","description":"Google Cloud Console project OAuth Client Secret\n\nGenerated from a custom OAuth Client, see docs"},"redirectUri":{"type":"string","description":"Google Cloud Console project OAuth Client Authorized redirect URI\n\nGenerated from a custom OAuth Client, see docs. multi-scrobbler will generate a default based on BASE_URL.\nOnly specify this if the default does not work for you."},"innertubeOptions":{"$ref":"#/definitions/InnertubeOptions","description":"Additional options for authorization and tailoring YTM client"}},"title":"YTMusicData"},"InnertubeOptions":{"type":"object","properties":{"po_token":{"type":"string","description":"Proof of Origin token\n\nMay be required if YTM starts returning 403"},"visitor_data":{"type":"string","description":"Visitor ID value found in VISITOR_INFO1_LIVE or visitorData cookie\n\nMay be required if YTM starts returning 403"},"account_index":{"type":"number","description":"If account login results in being able to choose multiple account, use a zero-based index to choose which one to monitor","examples":[0,1]},"location":{"type":"string"},"lang":{"type":"string"},"generate_session_locally":{"type":"boolean"},"device_category":{"type":"string"},"client_type":{"type":"string"},"timezone":{"type":"string"}},"title":"InnertubeOptions"},"MPRISSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPRISData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MPRISSourceConfig"},"MPRISData":{"type":"object","properties":{"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any players that START WITH these values, case-insensitive","examples":[["spotify","vlc"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","vlc"]]}},"title":"MPRISData"},"MopidySourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MopidyData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MopidySourceConfig"},"MopidyData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Mopidy HTTP server to connect to\n\nYou MUST have Mopidy-HTTP extension enabled: https://mopidy.com/ext/http\n\nmulti-scrobbler connects to the WebSocket endpoint that ultimately looks like this => `ws://localhost:6680/mopidy/ws/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `6680`\n* Path => `/mopidy/ws/`","examples":["ws://localhost:6680/mopidy/ws/"],"default":"ws://localhost:6680/mopidy/ws/"},"uriBlacklist":{"type":"array","items":{"type":"string"},"description":"Do not scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Don't scrobble tracks from soundcloud by adding 'soundcloud' to this list.\n\nList is ignored if uriWhitelist is used."},"uriWhitelist":{"type":"array","items":{"type":"string"},"description":"Only scrobble tracks whose URI STARTS WITH any of these strings, case-insensitive\n\nEX: Only scrobble tracks from soundcloud by adding 'soundcloud' to this list."},"albumBlacklist":{"type":"array","items":{"type":"string"},"description":"Remove album data that matches any case-insensitive string from this list when scrobbling,\n\nFor certain sources (Soundcloud) Mopidy does not have all track info (Album) and will instead use \"Soundcloud\" as the Album name. You can prevent multi-scrobbler from using this bad Album data by adding the fake name to this list. Multi-scrobbler will still scrobble the track, just without the bad data.","examples":[["Soundcloud","Mixcloud"]],"default":["Soundcloud"]}},"title":"MopidyData"},"ListenBrainzSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ListenBrainzSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `listenbrainz.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"ListenBrainzSourceConfig"},"ListenBrainzSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the ListenBrainz server, if not using the default","examples":["https://api.listenbrainz.org/"],"default":"https://api.listenbrainz.org/"},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["6794186bf-1157-4de6-80e5-uvb411f3ea2b"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","username"],"title":"ListenBrainzSourceData"},"JRiverSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/JRiverData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"JRiverSourceConfig"},"JRiverData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the JRiver HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:52199/MCWS/v1/`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `52199`\n* Path => `/MCWS/v1/`","examples":["http://localhost:52199/MCWS/v1/"],"default":"http://localhost:52199/MCWS/v1/"},"username":{"type":"string","description":"If you have enabled authentication, the username you set"},"password":{"type":"string","description":"If you have enabled authentication, the password you set"}},"required":["url"],"title":"JRiverData"},"KodiSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KodiData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"KodiSourceConfig"},"KodiData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL of the Kodi HTTP server to connect to\n\nmulti-scrobbler connects to the Web Service Interface endpoint that ultimately looks like this => `http://yourDomain:8080/jsonrpc`\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `http://`\n* Hostname => `localhost`\n* Port => `8080`\n* Path => `/jsonrpc`","examples":["http://localhost:8080/jsonrpc"],"default":"http://localhost:8080/jsonrpc"},"username":{"type":"string","description":"The username set for Remote Control via Web Sever"},"password":{"type":"string","description":"The password set for Remote Control via Web Sever"}},"required":["url","username","password"],"title":"KodiData"},"WebScrobblerSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/WebScrobblerData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"title":"WebScrobblerSourceConfig"},"WebScrobblerData":{"type":"object","properties":{"slug":{"type":["string","null"],"description":"The URL ending that should be used to identify scrobbles for this source\n\nIn WebScrobbler's Webhook you must set an 'API URL'. All MS WebScrobbler sources must start like:\n\nhttp://localhost:9078/api/webscrobbler\n\nIf you are using multiple WebScrobbler sources (scrobbles for many users) you must use a slug to match Sources with each users extension.\n\nExample:\n\n* slug: 'usera' => API URL: http://localhost:9078/api/webscrobbler/usera\n* slug: 'userb' => API URL: http://localhost:9078/api/webscrobbler/userb\n\nIf no slug is found from an extension's incoming webhook event the first WebScrobbler source without a slug will be used"},"blacklist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Block scrobbling from specific WebScrobbler Connectors","examples":[["youtube"]]},"whitelist":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Only allow scrobbling from specific WebScrobbler Connectors","examples":[["mixcloud","soundcloud","bandcamp"]]}},"title":"WebScrobblerData"},"ChromecastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/ChromecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"ChromecastSourceConfig"},"ChromecastData":{"type":"object","properties":{"blacklistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"whitelistDevices":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any cast device that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored\n\nUseful when used with auto discovery","examples":[["home-mini","family-tv"]]},"blacklistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"DO NOT scrobble from any application that START WITH these values, case-insensitive","examples":[["spotify","pandora"]]},"whitelistApps":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"ONLY scrobble from any application that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored","examples":[["spotify","pandora"]]},"useAvahi":{"type":"boolean","description":"Try to use Avahi and avahi-browse to resolve mDNS devices instead of native mDNS querying\n\nUseful for docker (alpine) container where mDNS resolution is not yet supported. Avahi socket must be exposed to the container and avahi-tools must be installed.","default":false},"useAutoDiscovery":{"type":"boolean","description":"Use mDNS to discovery Google Cast devices on your next automatically?\n\nIf not explicitly set then it is TRUE if `devices` is not set"},"devices":{"type":"array","items":{"$ref":"#/definitions/ChromecastDeviceInfo"},"description":"A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set"},"allowUnknownMedia":{"anyOf":[{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Chromecast Apps report a \"media type\" in the status info returned for whatever is currently playing\n\n* If set to TRUE then Music AND Generic/Unknown media will be tracked for ALL APPS\n* If set to FALSE then only media explicitly typed as Music will be tracked for ALL APPS\n* If set to a list then only Apps whose name contain one of these values, case-insensitive, will have Music AND Generic/Unknown tracked\n\nSee https://developers.google.com/cast/docs/media/messages#MediaInformation \"metadata\" property","default":false},"forceMediaRecognitionOn":{"type":"array","items":{"type":"string"},"description":"Media provided by any App whose name is listed here will ALWAYS be tracked, regardless of the \"media type\" reported\n\nApps will be recognized if they CONTAIN any of these values, case-insensitive"}},"title":"ChromecastData"},"ChromecastDeviceInfo":{"type":"object","properties":{"name":{"type":"string","description":"A friendly name to identify this device","examples":["MySmartTV"]},"address":{"type":"string","description":"The IP address of the device","examples":["192.168.0.115"]}},"required":["name","address"],"title":"ChromecastDeviceInfo"},"MalojaSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MalojaSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `maloja.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"MalojaSourceConfig"},"MalojaSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for maloja server","examples":["http://localhost:42010"]},"apiKey":{"type":"string","description":"API Key for Maloja server","examples":["myApiKey"]}},"required":["apiKey","url"],"title":"MalojaSourceData"},"MusikcubeSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusikcubeData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MusikcubeSourceConfig"},"MusikcubeData":{"type":"object","properties":{"url":{"type":"string","description":"URL of the Musikcube Websocket (Metadata) server to connect to\n\nYou MUST have enabled 'metadata' server and set a password: https://github.com/clangen/musikcube/wiki/remote-api-documentation\n * musikcube -> settings -> server setup\n\nThe URL you provide here will have all parts not explicitly defined filled in for you so if these are not the default you must define them.\n\nParts => [default value]\n\n* Protocol => `ws://`\n* Hostname => `localhost`\n* Port => `7905`","examples":["ws://localhost:7905"],"default":"ws://localhost:7905"},"password":{"type":"string","description":"Password set in Musikcube https://github.com/clangen/musikcube/wiki/remote-api-documentation\n\n* musikcube -> settings -> server setup -> password"},"device_id":{"type":"string"}},"required":["password"],"title":"MusikcubeData"},"MusicCastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MusicCastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"MusicCastSourceConfig"},"MusicCastData":{"type":"object","properties":{"url":{"type":"string","description":"The host or URL of the YamahaExtendedControl endpoint to use","examples":[["192.168.0.101","http://192.168.0.101/YamahaExtendedControl"]]}},"required":["url"],"title":"MusicCastData"},"MPDSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/MPDData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/MPDSourceOptions"}},"required":["data","options"],"title":"MPDSourceConfig"},"MPDData":{"type":"object","properties":{"url":{"type":"string","description":"URL:PORT of the MPD server to connect to\n\nTo use this you must have TCP connections enabled for your MPD server https://mpd.readthedocs.io/en/stable/user.html#client-connections","examples":["localhost:6600"],"default":"localhost:6600"},"path":{"type":"string","description":"If using socket specify the path instead of url.\n\ntrailing `~` is replaced by your home directory"},"password":{"type":"string","description":"Password for the server, if set https://mpd.readthedocs.io/en/stable/user.html#permissions-and-passwords"}},"title":"MPDData"},"MPDSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"MPDSourceOptions"},"VLCSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/VLCData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/VLCSourceOptions"}},"required":["data"],"title":"VLCSourceConfig"},"VLCData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"url":{"type":"string","description":"URL:PORT of the VLC server to connect to\n\nTo use this you must have the Web (http) interface module enabled and a password set https://foxxmd.github.io/multi-scrobbler/docs/configuration#vlc","examples":["localhost:8080"],"default":"localhost:8080"},"password":{"type":"string","description":"Password for the server"}},"required":["password"],"title":"VLCData"},"VLCSourceOptions":{"type":"object","properties":{"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"},"filenamePatterns":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A list of regular expressions to use to extract metadata (title, album, artist) from a filename\n\nUsed when VLC reports only the filename for the current audio track"},"logFilenamePatterns":{"type":"boolean","description":"Log to DEBUG when a filename-only track is matched or not matched by filenamePatterns","default":false},"dumpVlcMetadata":{"type":"boolean","description":"Dump all the metadata VLC reports for an audio track to DEBUG.\n\nUse this if reporting an issue with VLC not correctly capturing metadata for a track.","default":false}},"title":"VLCSourceOptions"},"IcecastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/IcecastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/IcecastSourceOptions"}},"required":["data"],"title":"IcecastSourceConfig"},"IcecastData":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/definitions/IcecastSource"}},"icestatsEndpoint":{"type":"string"},"statsEndpoint":{"type":"string"},"nextsongsEndpoint":{"type":"string"},"sevenhtmlEndpoint":{"type":"string"},"icyMetaInt":{"type":"number"},"url":{"type":"string","description":"The Icecast stream URL"}},"required":["url"],"title":"IcecastData"},"IcecastSource":{"type":"string","enum":["icy","ogg","icestats","stats","sevenhtml","nextsongs"],"title":"IcecastSource"},"IcecastSourceOptions":{"type":"object","properties":{"systemScrobble":{"type":"boolean","description":"For Sources that support manual listening, should MS default to scrobbling when no user interaction has occurred?\n\nIf not specified MS will use a Source's specific behavior, see Source's documentation."},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"maxPollRetries":{"type":"number","description":"default # of automatic polling restarts on error","default":5,"examples":[5]},"logPayload":{"type":"boolean","description":"* If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload) then setting this option to true will make MS log the payload JSON to DEBUG output\n* If this source is POLLING then it will log the raw data for each unique track/response the first time it is seen","default":false,"examples":[false]},"logFilterFailure":{"type":["boolean","string"],"enum":[false,"debug","warn"],"description":"If this source has INGRESS to MS and has filters this determines how MS logs when a payload (event) fails a defined filter (IE users/servers/library filters)\n\n* `false` => do not log\n* `debug` => log to DEBUG level\n* `warn` => log to WARN level (default)\n\nHint: This is useful if you are sure this source is setup correctly and you have multiple other sources. Set to `debug` or `false` to reduce log noise.","default":"warn","examples":["warn"]},"logPlayerState":{"type":"boolean","description":"For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output","default":false,"examples":[false]},"logToFile":{"anyOf":[{"type":"boolean","enum":[true]},{"$ref":"#/definitions/LogLevel"},{"$ref":"#/definitions/FileLogOptions"}],"description":"**Exprimental:** Log to a separate file for this Source.\n\nUseful for debugging long-running Sources"},"scrobbleBacklog":{"type":"boolean","description":"If this source\n\n* supports fetching a listen history\n* and this option is enabled\n\nthen on startup MS will attempt to scrobble the recent listens from that history","default":true,"examples":[true,false]},"scrobbleThresholds":{"$ref":"#/definitions/ScrobbleThresholds","description":"Set thresholds for when multi-scrobbler should consider a tracked play to be \"scrobbable\". If both duration and percent are defined then if either condition is met the track is scrobbled."},"scrobbleBacklogCount":{"type":"number","description":"The number of listens to fetch when scrobbling from backlog\n\n* Only applies if this source supports fetching a listen history\n* If not specified it defaults to the maximum number of listens the source API supports"},"playTransform":{"$ref":"#/definitions/PlayTransformOptions"}},"title":"IcecastSourceOptions"},"AzuracastSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/AzuracastData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"}},"required":["data"],"title":"AzuracastSourceConfig"},"AzuracastData":{"type":"object","properties":{"url":{"type":"string","description":"Base URL of the Azuracast instance\n\nThis does NOT include the station. If a station is included it will be ignored. Use `station` field to specify station, if necessary","examples":["https://radio.mydomain.tld","http://localhost:80"]},"station":{"type":"string","description":"The specific station to monitor\n\nScrobbling will only occur if any of the monitor conditions are met AND the station is ONLINE.\n\nTo monitor multiple stations create a Source for each station.","examples":["my-station-1"]},"monitorWhenListeners":{"type":["boolean","number"],"description":"Only activate scrobble monitoring if station\n\n* `true` => has any current listeners\n* `number` => has EQUAL TO or MORE THAN X number of listeners"},"monitorWhenLive":{"type":"boolean","description":"Only activate scrobble monitoring if station has a live DJ/Streamer","default":true},"apiKey":{"type":"string","description":"API Key used to access data about private streams\n\nhttps://www.azuracast.com/docs/developers/apis/#api-authentication"}},"required":["url","station"],"title":"AzuracastData"},"KoitoSourceConfig":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this source."},"data":{"$ref":"#/definitions/KoitoSourceData"},"enable":{"type":"boolean","description":"Should MS use this client/source? Defaults to true","default":true,"examples":[true]},"clients":{"type":"array","items":{"type":"string"},"description":"Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.","examples":[["MyMalojaConfigName","MyLastFMConfigName"]]},"options":{"$ref":"#/definitions/CommonSourceOptions"},"configureAs":{"type":"string","enum":["source"],"description":"When used in `koito.config` this tells multi-scrobbler whether to use this data to configure a source or client.","default":"source","examples":["source"]}},"required":["data"],"title":"KoitoSourceConfig"},"KoitoSourceData":{"type":"object","properties":{"interval":{"type":"number","description":"How long to wait before polling the source API for new tracks (in seconds)","default":10,"examples":[10]},"maxInterval":{"type":"number","description":"When there has been no new activity from the Source API multi-scrobbler will gradually increase the wait time between polling up to this value (in seconds)","default":30,"examples":[30]},"staleAfter":{"type":"number","description":"Number of seconds after which A Player is considered Stale\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Stale. When the Player becomes Stale:\n\n* The current listening session is ended. If the Player becomes active again a new listening session is started (Player will miss `interval` seconds of listening)\n* If the player has an existing session w/ track then MS attempts to scrobble it\n\nThis option DOES NOT need to be set. It is automatically calculated as (`interval` * 3) when not defined."},"orphanedAfter":{"type":"number","description":"Number of seconds after which A Player is considered Orphaned\n\nWhen Polling the source does not recieve data about a specific Player after X seconds it becomes Orphaned. When the Player becomes Orphaned:\n\n* The current Player session is ended and the Player is removed from MS\n* MS attempts to scrobble, if the Player has an existing session w/ track\n\nA Player should become Orphaned EQUAL TO OR AFTER it becomes Stale.\n\n* This option DOES NOT need to be set. It is automatically calculated as (`interval` * 5) when not defined.\n* If it is set it must be equal to or larger than `staleAfter` or (`interval * 3`)"},"maxRequestRetries":{"type":"number","description":"default # of http request retries a source/client can make before error is thrown","default":1,"examples":[1]},"retryMultiplier":{"type":"number","description":"default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)","default":1.5,"examples":[1.5]},"url":{"type":"string","description":"URL for the Koito server","examples":["http://192.168.0.100:4110"]},"token":{"type":"string","description":"User token for the user to scrobble for","examples":["pM195xPV98CDpk0QW47FIIOR8AKATAX5DblBF-Jq0t1MbbKL"]},"username":{"type":"string","description":"Username of the user to scrobble for"}},"required":["token","url","username"],"title":"KoitoSourceData"}}} \ No newline at end of file diff --git a/src/backend/index.ts b/src/backend/index.ts index aa85c734..5049d34a 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -21,6 +21,7 @@ import { createVegaGenerator } from './utils/SchemaUtils.js'; import ScrobbleClients from './scrobblers/ScrobbleClients.js'; import ScrobbleSources from './sources/ScrobbleSources.js'; import { Notifiers } from './notifier/Notifiers.js'; +import { TransferManager } from './transfer/TransferManager.js'; dayjs.extend(utc) dayjs.extend(isBetween); @@ -105,7 +106,9 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`) await root.items.cache().init(); - initServer(logger, appLoggerStream, output, scrobbleSources, scrobbleClients); + const transferManager = new TransferManager(scrobbleSources, scrobbleClients, logger); + + initServer(logger, appLoggerStream, output, scrobbleSources, scrobbleClients, transferManager); if(process.env.IS_LOCAL === 'true') { logger.info('multi-scrobbler can be run as a background service! See: https://foxxmd.github.io/multi-scrobbler/docs/installation/service'); diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index f827b64e..f657da0b 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -793,7 +793,9 @@ ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); } const currQueuedPlay = this.queuedScrobbles.shift(); - const [timeFrameValid, timeFrameValidLog] = this.timeFrameIsValid(currQueuedPlay.play); + // Skip timeframe validation for transfers (historical data) + const isTransfer = currQueuedPlay.source?.startsWith('transfer-'); + const [timeFrameValid, timeFrameValidLog] = isTransfer ? [true, ''] : this.timeFrameIsValid(currQueuedPlay.play); if (timeFrameValid && !(await this.alreadyScrobbled(this.transformPlay(currQueuedPlay.play, TRANSFORM_HOOK.preCompare)))) { const transformedScrobble = this.transformPlay(currQueuedPlay.play, TRANSFORM_HOOK.postCompare); try { @@ -878,7 +880,9 @@ ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); if (this.getLatestQueuePlayDate() !== undefined && this.scrobblesLastCheckedAt().unix() < this.getLatestQueuePlayDate().unix()) { await this.refreshScrobbles(); } - const [timeFrameValid, timeFrameValidLog] = this.timeFrameIsValid(deadScrobble.play); + // Skip timeframe validation for transfers (historical data) + const isTransfer = deadScrobble.source?.startsWith('transfer-'); + const [timeFrameValid, timeFrameValidLog] = isTransfer ? [true, ''] : this.timeFrameIsValid(deadScrobble.play); if (timeFrameValid && !(await this.alreadyScrobbled(this.transformPlay(deadScrobble.play, TRANSFORM_HOOK.preCompare)))) { const transformedScrobble = this.transformPlay(deadScrobble.play, TRANSFORM_HOOK.postCompare); try { @@ -939,6 +943,19 @@ ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); this.updateQueuedScrobblesCache(); } + cancelQueuedItemsBySource = (source: string): number => { + const beforeMain = this.queuedScrobbles.length; + const beforeDead = this.deadLetterScrobbles.length; + + this.queuedScrobbles = this.queuedScrobbles.filter(item => item.source !== source); + this.deadLetterScrobbles = this.deadLetterScrobbles.filter(item => item.source !== source); + + this.updateQueuedScrobblesCache(); + this.updateDeadLetterCache(); + + return (beforeMain + beforeDead) - (this.queuedScrobbles.length + this.deadLetterScrobbles.length); + } + protected addDeadLetterScrobble = (data: QueuedScrobble, error: (Error | string) = 'Unspecified error') => { let eString = ''; if(typeof error === 'string') { diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index 0612c18e..69d49c6d 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -33,6 +33,8 @@ import { setupTautulliRoutes } from "./tautulliRoutes.js"; import { setupWebscrobblerRoutes } from "./webscrobblerRoutes.js"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; +import { TransferManager } from "../transfer/TransferManager.js"; +import { TransferOptions } from "../transfer/TransferJob.js"; const maxBufferSize = 300; const output: Record> = {}; @@ -54,7 +56,7 @@ const getLogs = (minLevel: number, limit: number = maxBufferSize, sort: 'asc' | return allLogs.flat(1).sort((a, b) => a.time - b.time).slice(0, limit); } -export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: PassThrough, initialLogOutput: LogDataPretty[] = [], scrobbleSources: ScrobbleSources, scrobbleClients: ScrobbleClients) => { +export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: PassThrough, initialLogOutput: LogDataPretty[] = [], scrobbleSources: ScrobbleSources, scrobbleClients: ScrobbleClients, transferManager: TransferManager) => { for(const level of Object.keys(logger.levels.labels)) { output[level] = new FixedSizeList(maxBufferSize); } @@ -520,6 +522,49 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: return res.json({version: root.get('version')}); }); + app.getAsync('/api/transfer/sources-clients', async (req, res) => { + try { + const result = transferManager.getActiveSourcesAndClients(); + return res.json(result); + } catch (e) { + logger.error(`Error getting sources and clients: ${e.message}`); + return res.status(500).json({ error: e.message }); + } + }); + + app.postAsync('/api/transfer', bodyParser.json(), async (req, res) => { + try { + const options: TransferOptions = req.body; + const id = await transferManager.startTransfer(options); + return res.json({ id }); + } catch (e) { + logger.error(`Error starting transfer: ${e.message}`); + return res.status(400).json({ error: e.message }); + } + }); + + app.getAsync('/api/transfer/:id?', async (req, res) => { + try { + const { id } = req.params; + const result = transferManager.getTransferStatus(id); + return res.json(result); + } catch (e) { + logger.error(`Error getting transfer status: ${e.message}`); + return res.status(404).json({ error: e.message }); + } + }); + + app.deleteAsync('/api/transfer/:id', async (req, res) => { + try { + const { id } = req.params; + transferManager.cancelTransfer(id); + return res.status(200).json({ message: 'Transfer cancelled' }); + } catch (e) { + logger.error(`Error cancelling transfer: ${e.message}`); + return res.status(400).json({ error: e.message }); + } + }); + app.useAsync('/api/*', async (req, res) => { const remote = req.connection.remoteAddress; const proxyRemote = req.headers["x-forwarded-for"]; diff --git a/src/backend/server/index.ts b/src/backend/server/index.ts index 796db850..e5a464b0 100644 --- a/src/backend/server/index.ts +++ b/src/backend/server/index.ts @@ -15,11 +15,12 @@ import { getAddress } from "../utils/NetworkUtils.js"; import { setupApi } from "./api.js"; import ScrobbleSources from '../sources/ScrobbleSources.js'; import ScrobbleClients from '../scrobblers/ScrobbleClients.js'; +import { TransferManager } from '../transfer/TransferManager.js'; const app = addAsync(express()); const router = Router(); -export const initServer = async (parentLogger: Logger, appLoggerStream: PassThrough, initialOutput: LogDataPretty[] = [], sources: ScrobbleSources, clients: ScrobbleClients) => { +export const initServer = async (parentLogger: Logger, appLoggerStream: PassThrough, initialOutput: LogDataPretty[] = [], sources: ScrobbleSources, clients: ScrobbleClients, transferManager: TransferManager) => { const logger = childLogger(parentLogger, 'API'); // parentLogger.child({labels: ['API']}, mergeArr); @@ -50,7 +51,7 @@ export const initServer = async (parentLogger: Logger, appLoggerStream: PassThro const local = root.get('localUrl'); const localDefined = root.get('hasDefinedBaseUrl'); - setupApi(app, logger, appLoggerStream, initialOutput, sources, clients); + setupApi(app, logger, appLoggerStream, initialOutput, sources, clients, transferManager); const addy = getAddress(); const addresses: string[] = []; diff --git a/src/backend/transfer/TransferJob.ts b/src/backend/transfer/TransferJob.ts new file mode 100644 index 00000000..bc34508c --- /dev/null +++ b/src/backend/transfer/TransferJob.ts @@ -0,0 +1,402 @@ +import { childLogger, Logger } from "@foxxmd/logging"; +import dayjs, { Dayjs } from "dayjs"; +import { PlayObject } from "../../core/Atomic.js"; +import { buildTrackString } from "../../core/StringUtils.js"; +import { TRANSFORM_HOOK } from "../common/infrastructure/Atomic.js"; +import LastfmApiClient from "../common/vendor/LastfmApiClient.js"; +import { ListenbrainzApiClient } from "../common/vendor/ListenbrainzApiClient.js"; +import AbstractScrobbleClient from "../scrobblers/AbstractScrobbleClient.js"; +import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; +import AbstractSource from "../sources/AbstractSource.js"; +import LastfmSource from "../sources/LastfmSource.js"; +import ListenbrainzSource from "../sources/ListenbrainzSource.js"; +import ScrobbleSources from "../sources/ScrobbleSources.js"; +import { sortByOldestPlayDate } from "../utils.js"; + +export interface TransferOptions { + sourceName: string; + clientName: string; + playCount?: number; + fromDate?: string; + toDate?: string; +} + +export type TransferStatus = 'pending' | 'running' | 'completed' | 'failed'; + +export interface TransferProgress { + status: TransferStatus; + processed: number; + total: number; + queued: number; + duplicates: number; + errors: number; + currentPage?: number; + totalPages?: number; + startedAt?: Dayjs; + completedAt?: Dayjs; + currentError?: string; + currentTrack?: string; + rate?: number; +} + +export class TransferJob { + private transferId: string; + private sourceName: string; + private clientName: string; + private playCount?: number; + private fromDate?: Dayjs; + private toDate?: Dayjs; + private logger: Logger; + + private progress: TransferProgress; + private scrobbleSources: ScrobbleSources; + private scrobbleClients: ScrobbleClients; + private activeClient?: AbstractScrobbleClient; + + private shouldCancel: boolean = false; + + private readonly PAGE_SIZE = 200; + private readonly SLIDING_WINDOW_SIZE = 500; + private queuedCount: number = 0; + + constructor( + options: TransferOptions, + scrobbleSources: ScrobbleSources, + scrobbleClients: ScrobbleClients, + logger: Logger, + transferId: string + ) { + this.transferId = transferId; + this.sourceName = options.sourceName; + this.clientName = options.clientName; + this.playCount = options.playCount; + this.scrobbleSources = scrobbleSources; + this.scrobbleClients = scrobbleClients; + this.logger = childLogger(logger, ['Transfer', `${this.sourceName} -> ${this.clientName}`, transferId]); + + if (options.fromDate) { + this.fromDate = dayjs(options.fromDate); + } + if (options.toDate) { + this.toDate = dayjs(options.toDate); + } + + this.progress = { + status: 'pending', + processed: 0, + total: options.playCount || 0, + queued: 0, + duplicates: 0, + errors: 0, + }; + } + + public getProgress(): TransferProgress { + const progress = { ...this.progress }; + + if (progress.startedAt && progress.processed > 0) { + const elapsed = dayjs().diff(progress.startedAt, 'second'); + if (elapsed > 0) { + progress.rate = progress.processed / elapsed; + } + } + + // Update queued count from our counter + progress.queued = this.queuedCount; + + return progress; + } + + public cancel(): void { + this.shouldCancel = true; + this.logger.info('Cancel requested'); + + // Remove any queued items from the client's queue + if (this.activeClient) { + const transferSource = `transfer-${this.transferId}`; + const removed = this.activeClient.cancelQueuedItemsBySource(transferSource); + this.logger.info(`Removed ${removed} queued items from client queue`); + } + } + + public async run(): Promise { + this.progress.status = 'running'; + this.progress.startedAt = dayjs(); + + const mode = this.fromDate || this.toDate ? 'date range' : 'recent plays'; + this.logger.info(`Starting transfer (${mode}) from ${this.sourceName} to ${this.clientName}`); + + try { + const source = this.getSource(); + const client = this.getClient(); + this.activeClient = client; // Store for progress calculation + + if (!source.isReady()) { + throw new Error(`Source '${this.sourceName}' is not ready`); + } + + if (!client.isReady()) { + throw new Error(`Client '${this.clientName}' is not ready`); + } + + if (source instanceof LastfmSource) { + await this.runLastfmTransfer(source as LastfmSource, client); + } else if (source instanceof ListenbrainzSource) { + await this.runListenbrainzTransfer(source as ListenbrainzSource, client); + } else { + await this.runGenericTransfer(source, client); + } + + this.progress.status = 'completed'; + this.progress.completedAt = dayjs(); + this.logger.info(`Transfer completed: ${this.progress.queued} queued, ${this.progress.duplicates} duplicates, ${this.progress.errors} errors`); + + } catch (e) { + this.progress.status = 'failed'; + this.progress.completedAt = dayjs(); + this.progress.currentError = e.message; + this.logger.error(`Transfer failed: ${e.message}`); + throw e; + } + } + + private async runLastfmTransfer(source: LastfmSource, client: AbstractScrobbleClient): Promise { + const api = source.api as LastfmApiClient; + + let currentPage = 1; + let hasMorePages = true; + let totalPagesKnown = false; + + while (hasMorePages) { + this.logger.verbose(`Fetching page ${currentPage}...`); + this.progress.currentPage = currentPage; + + // If playCount is specified, only fetch as many as we need + let pageSize = this.PAGE_SIZE; + if (this.playCount) { + const remaining = this.playCount - this.progress.processed; + pageSize = Math.min(this.PAGE_SIZE, remaining); + if (pageSize <= 0) { + this.logger.info(`Reached play count limit of ${this.playCount}`); + break; + } + } + + const resp = await api.getRecentTracksWithPagination({ + page: currentPage, + limit: pageSize, + from: this.fromDate?.unix(), + to: this.toDate?.unix(), + }); + + const { + recenttracks: { + track: rawTracks = [], + '@attr': pageInfo + } + } = resp; + + if (pageInfo && !totalPagesKnown) { + const totalPages = parseInt(pageInfo.totalPages, 10); + const total = parseInt(pageInfo.total, 10); + + // If playCount is specified, cap the total at playCount + if (this.playCount) { + this.progress.total = Math.min(this.playCount, total); + // Calculate how many pages we'll actually need + this.progress.totalPages = Math.ceil(this.progress.total / this.PAGE_SIZE); + } else { + this.progress.total = total; + this.progress.totalPages = totalPages; + } + + totalPagesKnown = true; + this.logger.info(`Total pages in source: ${totalPages}, Total plays in source: ${total}, Will transfer: ${this.progress.total}, Expected pages: ${this.progress.totalPages}`); + } + + if (rawTracks.length === 0) { + hasMorePages = false; + break; + } + + const plays = rawTracks + .filter(t => t.date !== undefined) + .map(t => LastfmApiClient.formatPlayObj(t)) + .sort(sortByOldestPlayDate); + + if (plays.length > 0) { + await this.processPlaysWithSlidingWindow(plays, client); + } + + if (this.playCount && this.progress.processed >= this.playCount) { + this.logger.info(`Reached play count limit of ${this.playCount}`); + hasMorePages = false; + } else { + currentPage++; + if (pageInfo && currentPage > parseInt(pageInfo.totalPages, 10)) { + hasMorePages = false; + } + } + } + } + + private async runListenbrainzTransfer(source: ListenbrainzSource, client: AbstractScrobbleClient): Promise { + const api = source.api as ListenbrainzApiClient; + + let maxTs = this.toDate?.unix(); + let hasMorePlays = true; + let pageNum = 1; + + while (hasMorePlays) { + this.logger.verbose(`Fetching page ${pageNum} (max_ts: ${maxTs})...`); + this.progress.currentPage = pageNum; + + // If playCount is specified, only fetch as many as we need + let pageSize = this.PAGE_SIZE; + if (this.playCount) { + const remaining = this.playCount - this.progress.processed; + pageSize = Math.min(this.PAGE_SIZE, remaining); + if (pageSize <= 0) { + this.logger.info(`Reached play count limit of ${this.playCount}`); + break; + } + } + + const resp = await api.getUserListensWithPagination({ + count: pageSize, + minTs: this.fromDate?.unix(), + maxTs, + }); + + const { listens = [] } = resp; + + if (listens.length === 0) { + hasMorePlays = false; + break; + } + + const plays = listens + .map(l => ListenbrainzApiClient.formatPlayObj(l, {})) + .sort(sortByOldestPlayDate); + + if (plays.length > 0) { + await this.processPlaysWithSlidingWindow(plays, client); + + const oldestPlay = plays[0]; + if (oldestPlay.data.playDate) { + maxTs = oldestPlay.data.playDate.unix() - 1; + } + } + + if (this.playCount && this.progress.processed >= this.playCount) { + this.logger.info(`Reached play count limit of ${this.playCount}`); + hasMorePlays = false; + } + + pageNum++; + } + } + + private async runGenericTransfer(source: AbstractSource, client: AbstractScrobbleClient): Promise { + this.logger.verbose('Fetching plays from source...'); + const plays = await source.getRecentlyPlayed({ limit: this.playCount || 200 }); + + if (plays.length === 0) { + this.logger.warn('No plays returned from source'); + return; + } + + this.logger.info(`Fetched ${plays.length} plays from source`); + this.progress.total = plays.length; + + const sortedPlays = [...plays].sort(sortByOldestPlayDate); + await this.processPlaysWithSlidingWindow(sortedPlays, client); + } + + private async processPlaysWithSlidingWindow(plays: PlayObject[], client: AbstractScrobbleClient): Promise { + if (plays.length === 0) { + return; + } + + const oldest = plays[0].data.playDate; + const newest = plays[plays.length - 1].data.playDate; + + if (oldest && newest) { + this.logger.debug(`Refreshing client scrobbles for time range: ${oldest.format()} to ${newest.format()}`); + await client.refreshScrobbles(this.SLIDING_WINDOW_SIZE); + } else { + await client.refreshScrobbles(this.SLIDING_WINDOW_SIZE); + } + + for (let i = 0; i < plays.length; i++) { + const play = plays[i]; + + // Yield to event loop every 10 plays to keep UI responsive + if (i > 0 && i % 10 === 0) { + await new Promise(resolve => setImmediate(resolve)); + } + + if (this.shouldCancel) { + this.progress.status = 'failed'; + this.progress.currentError = 'Transfer cancelled by user'; + throw new Error('Transfer cancelled by user'); + } + + this.progress.currentTrack = buildTrackString(play); + + try { + await this.processPlay(play, client); + } catch (e) { + this.logger.error(`Error processing play ${buildTrackString(play)}: ${e.message}`); + this.progress.errors++; + this.progress.currentError = e.message; + } + + this.progress.processed++; + } + + this.progress.currentTrack = undefined; + } + + private async processPlay(play: PlayObject, client: AbstractScrobbleClient): Promise { + const transformedPlay = client.transformPlay(play, TRANSFORM_HOOK.preCompare); + + // NOTE: We skip timeFrameIsValid check during transfers because: + // 1. Transfers are for historical data that may be older than any existing scrobbles + // 2. timeFrameIsValid is designed to prevent re-scrobbling during normal operation + // 3. We have our own duplicate detection via time-range fetching + + const alreadyScrobbled = await client.alreadyScrobbled(transformedPlay); + if (alreadyScrobbled) { + this.logger.debug(`Skipping ${buildTrackString(play)}: already scrobbled`); + this.progress.duplicates++; + return; + } + + // Queue the play for scrobbling - the client will process it asynchronously + // and persist the queue to disk for crash recovery + const scrobblePlay = client.transformPlay(transformedPlay, TRANSFORM_HOOK.postCompare); + + // Queue with our transfer ID as the source - queueScrobble will handle tracking + client.queueScrobble(scrobblePlay, `transfer-${this.transferId}`); + this.queuedCount++; + + this.logger.verbose(`Queued for scrobbling: ${buildTrackString(scrobblePlay)}`); + } + + private getSource(): AbstractSource { + const source = this.scrobbleSources.getByName(this.sourceName); + if (!source) { + throw new Error(`Source '${this.sourceName}' not found`); + } + return source; + } + + private getClient(): AbstractScrobbleClient { + const client = this.scrobbleClients.getByName(this.clientName); + if (!client) { + throw new Error(`Client '${this.clientName}' not found`); + } + return client; + } +} diff --git a/src/backend/transfer/TransferManager.ts b/src/backend/transfer/TransferManager.ts new file mode 100644 index 00000000..a409a0c4 --- /dev/null +++ b/src/backend/transfer/TransferManager.ts @@ -0,0 +1,196 @@ +import { Logger } from "@foxxmd/logging"; +import { nanoid } from "nanoid"; +import { AsyncTask, SimpleIntervalJob, ToadScheduler } from "toad-scheduler"; +import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; +import ScrobbleSources from "../sources/ScrobbleSources.js"; +import { TransferJob, TransferOptions, TransferProgress } from "./TransferJob.js"; + +export interface TransferJobInfo { + id: string; + options: TransferOptions; + progress: TransferProgress; +} + +export class TransferManager { + private scheduler: ToadScheduler; + private jobs: Map; + private logger: Logger; + private scrobbleSources: ScrobbleSources; + private scrobbleClients: ScrobbleClients; + + // Keep completed jobs for 1 hour + private readonly COMPLETED_JOB_TTL = 60 * 60 * 1000; + + constructor( + scrobbleSources: ScrobbleSources, + scrobbleClients: ScrobbleClients, + logger: Logger + ) { + this.scheduler = new ToadScheduler(); + this.jobs = new Map(); + this.logger = logger; + this.scrobbleSources = scrobbleSources; + this.scrobbleClients = scrobbleClients; + + this.logger.info('Transfer Manager initialized'); + } + + public async startTransfer(options: TransferOptions): Promise { + this.validateTransferOptions(options); + + const id = nanoid(); + const job = new TransferJob( + options, + this.scrobbleSources, + this.scrobbleClients, + this.logger, + id + ); + + // Create a long-running AsyncTask (not interval-based) + const task = new AsyncTask( + `transfer-${id}`, + async () => { + try { + await job.run(); + } catch (e) { + this.logger.error(`Transfer ${id} failed: ${e.message}`); + } finally { + this.scheduleJobCleanup(id); + } + }, + (err: Error) => { + this.logger.error(`Unexpected error in transfer ${id}:`, err); + this.scheduleJobCleanup(id); + } + ); + + this.jobs.set(id, { job, scheduledJob: undefined }); + + const mode = options.fromDate || options.toDate ? 'date range' : `${options.playCount} plays`; + this.logger.info(`Started transfer ${id}: ${options.sourceName} -> ${options.clientName} (${mode})`); + + // Execute the task directly (not as an interval job) + task.execute(); + + return id; + } + + public getTransferStatus(id?: string): TransferJobInfo | TransferJobInfo[] { + if (id) { + const jobInfo = this.jobs.get(id); + if (!jobInfo) { + throw new Error(`Transfer job '${id}' not found`); + } + return this.jobToInfo(id, jobInfo.job); + } + + return Array.from(this.jobs.entries()).map(([jobId, { job }]) => + this.jobToInfo(jobId, job) + ); + } + + public cancelTransfer(id: string): void { + const jobInfo = this.jobs.get(id); + if (!jobInfo) { + throw new Error(`Transfer job '${id}' not found`); + } + jobInfo.job.cancel(); + } + + public getActiveSourcesAndClients(): { sources: string[]; clients: string[] } { + const sources = this.scrobbleSources.sources + .filter(s => s.isReady()) + .map(s => s.name); + + const clients = this.scrobbleClients.clients + .filter(c => c.isReady()) + .map(c => c.name); + + return { sources, clients }; + } + + private validateTransferOptions(options: TransferOptions): void { + const { sourceName, clientName, playCount, fromDate, toDate } = options; + + if (!sourceName || sourceName.trim() === '') { + throw new Error('Source name is required'); + } + + if (!clientName || clientName.trim() === '') { + throw new Error('Client name is required'); + } + + if (!playCount && !fromDate && !toDate) { + throw new Error('Either playCount or date range (fromDate/toDate) must be provided'); + } + + if (playCount !== undefined && playCount <= 0) { + throw new Error('Play count must be greater than 0'); + } + + const source = this.scrobbleSources.getByName(sourceName); + if (!source) { + throw new Error(`Source '${sourceName}' not found`); + } + + if (!source.isReady()) { + throw new Error(`Source '${sourceName}' is not ready`); + } + + const client = this.scrobbleClients.getByName(clientName); + if (!client) { + throw new Error(`Client '${clientName}' not found`); + } + + if (!client.isReady()) { + throw new Error(`Client '${clientName}' is not ready`); + } + + if (playCount !== undefined && playCount > 10000) { + this.logger.warn(`Play count ${playCount} is very large. This may take a long time.`); + } + } + + private jobToInfo(id: string, job: TransferJob): TransferJobInfo { + const options: TransferOptions = { + sourceName: job['sourceName'], + clientName: job['clientName'], + }; + + if (job['playCount'] !== undefined) { + options.playCount = job['playCount']; + } + if (job['fromDate'] !== undefined) { + options.fromDate = job['fromDate'].format('YYYY-MM-DD'); + } + if (job['toDate'] !== undefined) { + options.toDate = job['toDate'].format('YYYY-MM-DD'); + } + + return { + id, + options, + progress: job.getProgress(), + }; + } + + private scheduleJobCleanup(id: string): void { + setTimeout(() => { + const jobInfo = this.jobs.get(id); + if (jobInfo) { + const progress = jobInfo.job.getProgress(); + if (progress.status === 'completed' || progress.status === 'failed') { + this.logger.debug(`Cleaning up transfer job data ${id}`); + this.jobs.delete(id); + } + } + }, this.COMPLETED_JOB_TTL); + } + + public destroy(): void { + this.scheduler.stop(); + this.jobs.clear(); + this.logger.info('Transfer Manager destroyed'); + } +} diff --git a/src/client/App.tsx b/src/client/App.tsx index 9ef8da7d..842ba0db 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -13,6 +13,7 @@ import Dashboard from "./dashboard/dashboard"; import RecentPage from "./recent/RecentPage"; import ScrobbledPage from "./scrobbled/ScrobbledPage"; import DeadPage from "./deadLetter/DeadPage"; +import TransferPage from "./transfer/TransferPage"; import {clientUpdate, sourceUpdate} from "./status/ducks"; import {useEventSource, useEventSourceListener} from "@react-nano/use-event-source"; import Version from "./Version"; @@ -70,6 +71,10 @@ const routes: RouteObject[] = [ path: "/dead", element: , }, + { + path: "/transfer", + element: , + }, { path: "/docs", element: @@ -132,6 +137,9 @@ function App() { + + Transfer + Docs diff --git a/src/client/store.ts b/src/client/store.ts index 7a7afcf5..5f4e7bd8 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -11,6 +11,7 @@ import { scrobbledApi } from "./scrobbled/scrobbledDucks"; import { clientSlice, sourceSlice } from "./status/ducks"; import { statusApi } from './status/statusApi'; import { versionApi } from "./Version"; +import { transferApi } from "./transfer/transferApi"; export const store = configureStore({ reducer: { @@ -23,6 +24,7 @@ export const store = configureStore({ [scrobblerApi.reducerPath]: scrobblerApi.reducer, [sourceApi.reducerPath]: sourceApi.reducer, [versionApi.reducerPath]: versionApi.reducer, + [transferApi.reducerPath]: transferApi.reducer, //parts: statusReducer clients: clientSlice.reducer, sources: sourceSlice.reducer, @@ -32,7 +34,7 @@ export const store = configureStore({ // Adding the api middleware enables caching, invalidation, polling, // and other useful features of `rtk-query`. middleware: (getDefaultMiddleware) => - getDefaultMiddleware().concat([statusApi.middleware, logsApi.middleware, recentApi.middleware, scrobbledApi.middleware, deadApi.middleware, scrobblerApi.middleware, sourceApi.middleware, versionApi.middleware]), + getDefaultMiddleware().concat([statusApi.middleware, logsApi.middleware, recentApi.middleware, scrobbledApi.middleware, deadApi.middleware, scrobblerApi.middleware, sourceApi.middleware, versionApi.middleware, transferApi.middleware]), }) // optional, but required for refetchOnFocus/refetchOnReconnect behaviors diff --git a/src/client/transfer/TransferPage.tsx b/src/client/transfer/TransferPage.tsx new file mode 100644 index 00000000..c56af095 --- /dev/null +++ b/src/client/transfer/TransferPage.tsx @@ -0,0 +1,222 @@ +import React, { useState } from 'react'; +import { useGetSourcesClientsQuery, useStartTransferMutation, useGetTransfersQuery } from './transferApi'; +import TransferStatus from './TransferStatus'; + +type TransferMode = 'recent' | 'dateRange'; + +const TransferPage = () => { + const [mode, setMode] = useState('recent'); + const [sourceName, setSourceName] = useState(''); + const [clientName, setClientName] = useState(''); + const [playCount, setPlayCount] = useState(500); + const [fromDate, setFromDate] = useState(''); + const [toDate, setToDate] = useState(''); + + const { data: sourcesClients, isLoading: isLoadingSC } = useGetSourcesClientsQuery(); + const { data: transfers = [], refetch: refetchTransfers } = useGetTransfersQuery(undefined, { + pollingInterval: 2000, + }); + const [startTransfer, { isLoading: isStarting, error: startError }] = useStartTransferMutation(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!sourceName || !clientName) { + return; + } + + try { + const options: any = { + sourceName, + clientName, + }; + + if (mode === 'recent') { + if (playCount <= 0) { + return; + } + options.playCount = playCount; + } else { + if (fromDate) { + options.fromDate = fromDate; + } + if (toDate) { + options.toDate = toDate; + } + } + + await startTransfer(options).unwrap(); + refetchTransfers(); + } catch (err) { + console.error('Failed to start transfer:', err); + } + }; + + return ( +
+
+
+

Transfer Plays

+
+
+

+ Transfer plays from a source to a client. This is useful for backfilling your scrobble history between accounts. +

+ + {isLoadingSC ? ( +
Loading sources and clients...
+ ) : ( +
+
+ + +
+ +
+ + +
+ +
+ +
+ + +
+
+ + {mode === 'recent' ? ( +
+ + setPlayCount(parseInt(e.target.value))} + className="w-full p-2 bg-gray-600 border border-gray-500 rounded text-white" + required + /> +

+ Transfer the most recent {playCount} plays from the source. +

+
+ ) : ( +
+
+ + setFromDate(e.target.value)} + className="w-full p-2 bg-gray-600 border border-gray-500 rounded text-white" + /> +

+ Leave empty to start from the oldest available play +

+
+
+ + setToDate(e.target.value)} + className="w-full p-2 bg-gray-600 border border-gray-500 rounded text-white" + /> +

+ Leave empty to transfer up to the most recent play +

+
+

+ ⚠️ Date range transfers may take a long time for large histories. The transfer runs in the background. +

+
+ )} + + {startError && ( +
+ {'data' in startError && startError.data && typeof startError.data === 'object' && 'error' in startError.data + ? String(startError.data.error) + : 'Failed to start transfer'} +
+ )} + + +
+ )} +
+
+ +
+
+

Active & Recent Transfers

+
+
+ {transfers.length === 0 ? ( +

No transfers yet. Start one above!

+ ) : ( +
+ {transfers.map(transfer => ( + + ))} +
+ )} +
+
+
+ ); +}; + +export default TransferPage; diff --git a/src/client/transfer/TransferStatus.tsx b/src/client/transfer/TransferStatus.tsx new file mode 100644 index 00000000..35d978bd --- /dev/null +++ b/src/client/transfer/TransferStatus.tsx @@ -0,0 +1,192 @@ +import React from 'react'; +import { TransferJobInfo, useCancelTransferMutation } from './transferApi'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import duration from 'dayjs/plugin/duration'; + +dayjs.extend(relativeTime); +dayjs.extend(duration); + +interface TransferStatusProps { + transfer: TransferJobInfo; +} + +const TransferStatus: React.FC = ({ transfer }) => { + const { id, options, progress } = transfer; + const { status, processed, total, queued, duplicates, errors, startedAt, completedAt, currentError, currentTrack, rate } = progress; + + const [cancelTransfer, { isLoading: isCancelling }] = useCancelTransferMutation(); + + const handleCancel = async () => { + if (window.confirm('Are you sure you want to cancel this transfer?')) { + try { + await cancelTransfer(id).unwrap(); + } catch (err) { + console.error('Failed to cancel transfer:', err); + } + } + }; + + const getElapsedTime = () => { + if (!startedAt) return null; + const start = dayjs(startedAt); + const end = completedAt ? dayjs(completedAt) : dayjs(); + const diff = end.diff(start, 'second'); + return dayjs.duration(diff, 'seconds').format('HH:mm:ss'); + }; + + const getEstimatedTimeRemaining = () => { + if (!rate || rate === 0 || total === 0 || processed >= total) return null; + const remaining = total - processed; + const secondsRemaining = remaining / rate; + return dayjs.duration(secondsRemaining, 'seconds').format('HH:mm:ss'); + }; + + const getStatusColor = () => { + switch (status) { + case 'running': + return 'bg-blue-600'; + case 'completed': + return 'bg-green-600'; + case 'failed': + return 'bg-red-600'; + default: + return 'bg-gray-600'; + } + }; + + const getStatusText = () => { + switch (status) { + case 'pending': + return 'Pending'; + case 'running': + return 'Running'; + case 'completed': + return 'Completed'; + case 'failed': + return 'Failed'; + default: + return 'Unknown'; + } + }; + + const elapsedTime = getElapsedTime(); + const estimatedTimeRemaining = getEstimatedTimeRemaining(); + + const progressPercentage = total > 0 ? Math.round((processed / total) * 100) : 0; + + return ( +
+
+
+
+ {options.sourceName} → {options.clientName} +
+
+ {options.playCount ? ( + `${options.playCount} plays` + ) : options.fromDate || options.toDate ? ( + `${options.fromDate || 'start'} → ${options.toDate || 'now'}` + ) : ( + 'plays' + )} +
+
+ + {getStatusText()} + +
+ + {status === 'running' && ( +
+
+ Progress + {progressPercentage}% +
+
+
+
+
+ )} + +
+
+
Processed
+
{processed} / {total > 0 ? total : '?'}
+
+
+
Queued
+
{queued}
+
+
+
Duplicates
+
{duplicates}
+
+
+
Errors
+
{errors}
+
+
+ + {progress.currentPage !== undefined && ( +
+ Page: {progress.currentPage} {progress.totalPages ? `/ ${progress.totalPages}` : ''} +
+ )} + + {currentTrack && status === 'running' && ( +
+ Currently processing: {currentTrack} +
+ )} + + {elapsedTime && ( +
+ Elapsed: {elapsedTime} + {estimatedTimeRemaining && status === 'running' && ( + + Estimated remaining: {estimatedTimeRemaining} + + )} + {rate && rate > 0 && status === 'running' && ( + + Rate: {rate.toFixed(1)} plays/sec + + )} +
+ )} + + {currentError && ( +
+ Error: {currentError} +
+ )} + +
+
+ {startedAt && ( + Started {dayjs(startedAt).fromNow()} + )} + {completedAt && status !== 'running' && ( + • Completed {dayjs(completedAt).fromNow()} + )} +
+ + {status === 'running' && ( + + )} +
+
+ ); +}; + +export default TransferStatus; diff --git a/src/client/transfer/transferApi.ts b/src/client/transfer/transferApi.ts new file mode 100644 index 00000000..e4fc2e71 --- /dev/null +++ b/src/client/transfer/transferApi.ts @@ -0,0 +1,80 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react/index"; + +export interface TransferOptions { + sourceName: string; + clientName: string; + playCount?: number; + fromDate?: string; + toDate?: string; +} + +export type TransferStatus = 'pending' | 'running' | 'completed' | 'failed'; + +export interface TransferProgress { + status: TransferStatus; + processed: number; + total: number; + queued: number; + duplicates: number; + errors: number; + currentPage?: number; + totalPages?: number; + startedAt?: string; + completedAt?: string; + currentError?: string; + currentTrack?: string; + rate?: number; +} + +export interface TransferJobInfo { + id: string; + options: TransferOptions; + progress: TransferProgress; +} + +export interface SourcesClientsResponse { + sources: string[]; + clients: string[]; +} + +export const transferApi = createApi({ + reducerPath: 'transferApi', + baseQuery: fetchBaseQuery({ baseUrl: './api/' }), + tagTypes: ['Transfer'], + endpoints: (builder) => ({ + getSourcesClients: builder.query({ + query: () => 'transfer/sources-clients', + }), + startTransfer: builder.mutation<{ id: string }, TransferOptions>({ + query: (options) => ({ + url: 'transfer', + method: 'POST', + body: options, + }), + invalidatesTags: ['Transfer'], + }), + getTransfers: builder.query({ + query: () => 'transfer', + providesTags: ['Transfer'], + }), + getTransfer: builder.query({ + query: (id) => `transfer/${id}`, + providesTags: ['Transfer'], + }), + cancelTransfer: builder.mutation<{ message: string }, string>({ + query: (id) => ({ + url: `transfer/${id}`, + method: 'DELETE', + }), + invalidatesTags: ['Transfer'], + }), + }), +}); + +export const { + useGetSourcesClientsQuery, + useStartTransferMutation, + useGetTransfersQuery, + useGetTransferQuery, + useCancelTransferMutation, +} = transferApi; From 953ec479e15e0c6e8d6a1497301685cfbe1ba8dd Mon Sep 17 00:00:00 2001 From: Rostislav Raykov Date: Sat, 1 Nov 2025 22:22:01 +0100 Subject: [PATCH 4/6] fix(transfer): Improve duplicate detection and error handling --- .../common/vendor/koito/KoitoApiClient.ts | 31 +++++++ src/backend/scrobblers/LastfmScrobbler.ts | 46 ++++++++++ .../scrobblers/ListenbrainzScrobbler.ts | 39 +++++++++ src/backend/transfer/TransferJob.ts | 86 +++++++++++++++---- src/backend/transfer/TransferManager.ts | 38 ++++++++ src/backend/utils/StringUtils.ts | 9 +- src/backend/utils/TimeUtils.ts | 12 ++- 7 files changed, 239 insertions(+), 22 deletions(-) diff --git a/src/backend/common/vendor/koito/KoitoApiClient.ts b/src/backend/common/vendor/koito/KoitoApiClient.ts index 3991826a..97ceb696 100644 --- a/src/backend/common/vendor/koito/KoitoApiClient.ts +++ b/src/backend/common/vendor/koito/KoitoApiClient.ts @@ -144,6 +144,37 @@ export class KoitoApiClient extends AbstractApiClient { } } + getUserListensWithPagination = async (options: { + count?: number; + minTs?: number; + maxTs?: number; + } = {}): Promise => { + const { count = 100, minTs, maxTs } = options; + + try { + const query: any = { count }; + if (minTs !== undefined) { + query.min_ts = minTs; + } + if (maxTs !== undefined) { + query.max_ts = maxTs; + } + + const resp = await this.callApi(request + .get(`${joinedUrl(this.url.url, '/apis/listenbrainz/1/user', this.config.username, 'listens')}`) + .timeout({ + response: 15000, + deadline: 30000 + }) + .query(query)); + + const { body: { payload } } = resp as any; + return payload as ListensResponse; + } catch (e) { + throw e; + } + } + getRecentlyPlayed = async (maxTracks: number): Promise => { try { const resp = await this.getUserListens(maxTracks); diff --git a/src/backend/scrobblers/LastfmScrobbler.ts b/src/backend/scrobblers/LastfmScrobbler.ts index 27bf9808..c4939be3 100644 --- a/src/backend/scrobblers/LastfmScrobbler.ts +++ b/src/backend/scrobblers/LastfmScrobbler.ts @@ -1,4 +1,5 @@ import { Logger } from "@foxxmd/logging"; +import dayjs, { Dayjs } from "dayjs"; import EventEmitter from "events"; import { NowPlayingResponse, TrackScrobbleResponse, UserGetRecentTracksResponse } from "lastfm-node-client"; import { PlayObject } from "../../core/Atomic.js"; @@ -98,6 +99,51 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { } } + getScrobblesForTimeRange = async (fromDate?: Dayjs, toDate?: Dayjs, limit: number = 1000): Promise => { + const allPlays: PlayObject[] = []; + let currentPage = 1; + const perPage = 200; + + while (allPlays.length < limit) { + const resp = await this.api.getRecentTracksWithPagination({ + page: currentPage, + limit: perPage, + from: fromDate?.unix(), + to: toDate?.unix(), + }); + + const { + recenttracks: { + track: rawTracks = [], + '@attr': pageInfo + } = {} + } = resp; + + if (rawTracks.length === 0) { + break; + } + + const plays = rawTracks + .filter(t => t.date !== undefined) + .map(t => LastfmApiClient.formatPlayObj(t)) + .filter(p => p.data.playDate && p.data.playDate.isValid()); // Filter out plays with invalid dates + + allPlays.push(...plays); + + if (allPlays.length >= limit) { + break; + } + + if (pageInfo && currentPage >= parseInt(pageInfo.totalPages, 10)) { + break; + } + + currentPage++; + } + + return allPlays.slice(0, limit); + } + cleanSourceSearchTitle = (playObj: PlayObject) => { const { data: { diff --git a/src/backend/scrobblers/ListenbrainzScrobbler.ts b/src/backend/scrobblers/ListenbrainzScrobbler.ts index 4edf5318..a5c12386 100644 --- a/src/backend/scrobblers/ListenbrainzScrobbler.ts +++ b/src/backend/scrobblers/ListenbrainzScrobbler.ts @@ -1,4 +1,5 @@ import { Logger } from "@foxxmd/logging"; +import dayjs, { Dayjs } from "dayjs"; import EventEmitter from "events"; import { PlayObject } from "../../core/Atomic.js"; import { buildTrackString, capitalize } from "../../core/StringUtils.js"; @@ -69,6 +70,44 @@ export default class ListenbrainzScrobbler extends AbstractScrobbleClient { return await this.api.getRecentlyPlayed(limit); } + getScrobblesForTimeRange = async (fromDate?: Dayjs, toDate?: Dayjs, limit: number = 1000): Promise => { + const allPlays: PlayObject[] = []; + let maxTs = toDate?.unix(); + const minTs = fromDate?.unix(); + + while (allPlays.length < limit) { + const batchSize = Math.min(100, limit - allPlays.length); + const resp = await this.api.getUserListensWithPagination({ + count: batchSize, + minTs, + maxTs, + }); + + if (!resp.listens || resp.listens.length === 0) { + break; + } + + const plays = resp.listens + .map(l => ListenbrainzApiClient.formatPlayObj(l, {})) + .filter(p => p.data.playDate && p.data.playDate.isValid()); // Filter out plays with invalid dates + + allPlays.push(...plays); + + if (plays.length < batchSize) { + break; + } + + const oldestPlay = plays[plays.length - 1]; + if (oldestPlay.data.playDate) { + maxTs = oldestPlay.data.playDate.unix() - 1; + } else { + break; + } + } + + return allPlays; + } + alreadyScrobbled = async (playObj: PlayObject, log = false) => (await this.existingScrobble(playObj)) !== undefined public playToClientPayload(playObj: PlayObject): ListenPayload { diff --git a/src/backend/transfer/TransferJob.ts b/src/backend/transfer/TransferJob.ts index bc34508c..46a450f5 100644 --- a/src/backend/transfer/TransferJob.ts +++ b/src/backend/transfer/TransferJob.ts @@ -197,21 +197,12 @@ export class TransferJob { } = resp; if (pageInfo && !totalPagesKnown) { - const totalPages = parseInt(pageInfo.totalPages, 10); - const total = parseInt(pageInfo.total, 10); - - // If playCount is specified, cap the total at playCount - if (this.playCount) { - this.progress.total = Math.min(this.playCount, total); - // Calculate how many pages we'll actually need - this.progress.totalPages = Math.ceil(this.progress.total / this.PAGE_SIZE); - } else { - this.progress.total = total; - this.progress.totalPages = totalPages; - } + const apiTotal = parseInt(pageInfo.total, 10); + this.progress.total = this.playCount ? Math.min(this.playCount, apiTotal) : apiTotal; + this.progress.totalPages = Math.ceil(this.progress.total / this.PAGE_SIZE); totalPagesKnown = true; - this.logger.info(`Total pages in source: ${totalPages}, Total plays in source: ${total}, Will transfer: ${this.progress.total}, Expected pages: ${this.progress.totalPages}`); + this.logger.info(`Total plays in source: ${apiTotal}, Will transfer: ${this.progress.total}, Expected pages: ${this.progress.totalPages}`); } if (rawTracks.length === 0) { @@ -313,6 +304,8 @@ export class TransferJob { await this.processPlaysWithSlidingWindow(sortedPlays, client); } + private timeRangeScrobbles: PlayObject[] = []; + private async processPlaysWithSlidingWindow(plays: PlayObject[], client: AbstractScrobbleClient): Promise { if (plays.length === 0) { return; @@ -321,11 +314,33 @@ export class TransferJob { const oldest = plays[0].data.playDate; const newest = plays[plays.length - 1].data.playDate; + // Fetch scrobbles for the specific time range being processed if (oldest && newest) { - this.logger.debug(`Refreshing client scrobbles for time range: ${oldest.format()} to ${newest.format()}`); - await client.refreshScrobbles(this.SLIDING_WINDOW_SIZE); + this.logger.debug(`Fetching client scrobbles for time range: ${oldest.format()} to ${newest.format()}`); + + // Check if client supports time-range fetching + if ('getScrobblesForTimeRange' in client && typeof (client as any).getScrobblesForTimeRange === 'function') { + try { + // Add a buffer before/after to catch nearby scrobbles + const bufferHours = 24; + const fromDate = oldest.subtract(bufferHours, 'hours'); + const toDate = newest.add(bufferHours, 'hours'); + + this.timeRangeScrobbles = await (client as any).getScrobblesForTimeRange(fromDate, toDate, this.SLIDING_WINDOW_SIZE); + this.logger.debug(`Fetched ${this.timeRangeScrobbles.length} scrobbles from ${this.clientName} for duplicate detection`); + } catch (e) { + this.logger.error(`Error fetching scrobbles for time range: ${e.message}`); + throw e; + } + } else { + // Fallback to regular refresh (will only work for recent scrobbles) + this.logger.warn('Client does not support time-range fetching, falling back to regular refresh (may not detect duplicates for old scrobbles)'); + await client.refreshScrobbles(this.SLIDING_WINDOW_SIZE); + this.timeRangeScrobbles = []; + } } else { await client.refreshScrobbles(this.SLIDING_WINDOW_SIZE); + this.timeRangeScrobbles = []; } for (let i = 0; i < plays.length; i++) { @@ -358,6 +373,37 @@ export class TransferJob { this.progress.currentTrack = undefined; } + private isAlreadyScrobbledInTimeRange(play: PlayObject): boolean { + if (this.timeRangeScrobbles.length === 0) { + return false; + } + + const playDate = play.data.playDate; + if (!playDate) { + return false; + } + + // Check for matching scrobble (same track, artist, and similar timestamp) + return this.timeRangeScrobbles.some(scrobbled => { + const scrobbledDate = scrobbled.data.playDate; + if (!scrobbledDate) { + return false; + } + + // Check if timestamps are within 30 seconds of each other + const timeDiffSeconds = Math.abs(playDate.diff(scrobbledDate, 'second')); + if (timeDiffSeconds > 30) { + return false; + } + + // Check if track and artists match + const trackMatch = play.data.track?.toLowerCase() === scrobbled.data.track?.toLowerCase(); + const artistsMatch = play.data.artists?.join(',').toLowerCase() === scrobbled.data.artists?.join(',').toLowerCase(); + + return trackMatch && artistsMatch; + }); + } + private async processPlay(play: PlayObject, client: AbstractScrobbleClient): Promise { const transformedPlay = client.transformPlay(play, TRANSFORM_HOOK.preCompare); @@ -366,9 +412,17 @@ export class TransferJob { // 2. timeFrameIsValid is designed to prevent re-scrobbling during normal operation // 3. We have our own duplicate detection via time-range fetching + // Check against our time-range-specific scrobbles (the actual duplicate detection) + if (this.isAlreadyScrobbledInTimeRange(transformedPlay)) { + this.logger.verbose(`DUPLICATE (time-range): ${buildTrackString(play)} - found in ${this.clientName}'s ${this.timeRangeScrobbles.length} scrobbles`); + this.progress.duplicates++; + return; + } + + // Check using the client's built-in method (for recently added scrobbles from this transfer) const alreadyScrobbled = await client.alreadyScrobbled(transformedPlay); if (alreadyScrobbled) { - this.logger.debug(`Skipping ${buildTrackString(play)}: already scrobbled`); + this.logger.verbose(`DUPLICATE (recent): ${buildTrackString(play)} - found in ${this.clientName}'s recent scrobbles`); this.progress.duplicates++; return; } diff --git a/src/backend/transfer/TransferManager.ts b/src/backend/transfer/TransferManager.ts index a409a0c4..22879294 100644 --- a/src/backend/transfer/TransferManager.ts +++ b/src/backend/transfer/TransferManager.ts @@ -129,6 +129,39 @@ export class TransferManager { throw new Error('Play count must be greater than 0'); } + // Validate date range if provided + if (fromDate || toDate) { + const now = new Date(); + + if (fromDate) { + const from = new Date(fromDate); + if (isNaN(from.getTime())) { + throw new Error('Invalid fromDate format'); + } + if (from > now) { + throw new Error('fromDate cannot be in the future'); + } + } + + if (toDate) { + const to = new Date(toDate); + if (isNaN(to.getTime())) { + throw new Error('Invalid toDate format'); + } + if (to > now) { + throw new Error('toDate cannot be in the future'); + } + } + + if (fromDate && toDate) { + const from = new Date(fromDate); + const to = new Date(toDate); + if (from > to) { + throw new Error('fromDate must be before toDate'); + } + } + } + const source = this.scrobbleSources.getByName(sourceName); if (!source) { throw new Error(`Source '${sourceName}' not found`); @@ -147,6 +180,11 @@ export class TransferManager { throw new Error(`Client '${clientName}' is not ready`); } + // Check if client supports time-range fetching (required for duplicate detection) + if (!('getScrobblesForTimeRange' in client) || typeof (client as any).getScrobblesForTimeRange !== 'function') { + throw new Error(`Client '${clientName}' does not support time-range fetching, which is required for accurate duplicate detection during transfers. Supported clients: Last.fm, Listenbrainz`); + } + if (playCount !== undefined && playCount > 10000) { this.logger.warn(`Play count ${playCount} is very large. This may take a long time.`); } diff --git a/src/backend/utils/StringUtils.ts b/src/backend/utils/StringUtils.ts index 95002ab6..76e9b550 100644 --- a/src/backend/utils/StringUtils.ts +++ b/src/backend/utils/StringUtils.ts @@ -12,7 +12,7 @@ export const SYMBOLS_WHITESPACE_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+| export const SYMBOLS_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\[\]/]/g); export const MULTI_WHITESPACE_REGEX = new RegExp(/\s{2,}/g); -export const uniqueNormalizedStrArr = (arr: string[]): string[] => arr.reduce((acc: string[], curr) => { +export const uniqueNormalizedStrArr = (arr: string[]): string[] => arr.filter(x => x != null).reduce((acc: string[], curr) => { const normalizedCurr = normalizeStr(curr) if (!acc.some(x => normalizeStr(x) === normalizedCurr)) { return acc.concat(curr); @@ -21,6 +21,9 @@ export const uniqueNormalizedStrArr = (arr: string[]): string[] => arr.reduce((a }, []) // https://stackoverflow.com/a/37511463/1469797 export const normalizeStr = (str: string, options?: {keepSingleWhitespace?: boolean}): string => { + if (!str) { + return ''; + } const {keepSingleWhitespace = false} = options || {}; const normal = str.normalize('NFD').replace(/[\u0300-\u036f]/g, ""); if(!keepSingleWhitespace) { @@ -96,7 +99,7 @@ export const PRIMARY_SECONDARY_SECTIONS_REGEX = new RegExp(/^(?.+?)(? { - if (str.trim() === '') { + if (!str || str.trim() === '') { return undefined; } @@ -136,7 +139,7 @@ export const parseCredits = (str: string, delimiters?: boolean | string[]): Play return undefined; } export const parseArtistCredits = (str: string, delimiters?: boolean | string[], ignoreGlobalAmpersand?: boolean): PlayCredits | undefined => { - if (str.trim() === '') { + if (!str || str.trim() === '') { return undefined; } let delims: string[] | undefined; diff --git a/src/backend/utils/TimeUtils.ts b/src/backend/utils/TimeUtils.ts index a270e775..799d179b 100644 --- a/src/backend/utils/TimeUtils.ts +++ b/src/backend/utils/TimeUtils.ts @@ -34,10 +34,16 @@ dayjs.extend(isToday); export const temporalPlayComparisonSummary = (data: TemporalPlayComparison, existingPlay?: PlayObject, candidatePlay?: PlayObject) => { const parts: string[] = []; if (existingPlay !== undefined && candidatePlay !== undefined) { - if (existingPlay.data.playDate.isSame(candidatePlay.data.playDate, 'day')) { - parts.push(`Existing: ${existingPlay.data.playDate.format('HH:mm:ssZ')} - Candidate: ${candidatePlay.data.playDate.format('HH:mm:ssZ')}`); + const existingDate = existingPlay.data.playDate; + const candidateDate = candidatePlay.data.playDate; + + // Check if dates are valid before comparing/formatting + if (!existingDate?.isValid() || !candidateDate?.isValid()) { + parts.push(`Existing: ${existingDate?.isValid() ? existingDate.toISOString() : 'Invalid Date'} - Candidate: ${candidateDate?.isValid() ? candidateDate.toISOString() : 'Invalid Date'}`); + } else if (existingDate.isSame(candidateDate, 'day')) { + parts.push(`Existing: ${existingDate.format('HH:mm:ssZ')} - Candidate: ${candidateDate.format('HH:mm:ssZ')}`); } else { - parts.push(`Existing: ${existingPlay.data.playDate.toISOString()} - Candidate: ${candidatePlay.data.playDate.toISOString()}`); + parts.push(`Existing: ${existingDate.toISOString()} - Candidate: ${candidateDate.toISOString()}`); } } parts.push(`Temporal Sameness: ${capitalize(temporalAccuracyToString(data.match))}`); From 2f804ef170913d921169abd18f764a26ad2e9253 Mon Sep 17 00:00:00 2001 From: Rostislav Raykov Date: Sun, 2 Nov 2025 16:03:51 +0100 Subject: [PATCH 5/6] test(transfer): Add validation tests for transfer feature --- src/backend/tests/transfer/transfer.test.ts | 199 ++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 src/backend/tests/transfer/transfer.test.ts diff --git a/src/backend/tests/transfer/transfer.test.ts b/src/backend/tests/transfer/transfer.test.ts new file mode 100644 index 00000000..b148b45d --- /dev/null +++ b/src/backend/tests/transfer/transfer.test.ts @@ -0,0 +1,199 @@ +import { loggerTest } from "@foxxmd/logging"; +import chai, { assert, expect } from 'chai'; +import asPromised from 'chai-as-promised'; +import clone from 'clone'; +import dayjs from "dayjs"; +import EventEmitter from "events"; +import { describe, it, beforeEach } from 'mocha'; +import { PlayObject } from "../../../core/Atomic.js"; +import { TransferManager } from "../../transfer/TransferManager.js"; +import { TransferJob } from "../../transfer/TransferJob.js"; +import { generatePlay, generatePlays, normalizePlays } from "../utils/PlayTestUtils.js"; +import ScrobbleSources from "../../sources/ScrobbleSources.js"; +import ScrobbleClients from "../../scrobblers/ScrobbleClients.js"; +import AbstractScrobbleClient from "../../scrobblers/AbstractScrobbleClient.js"; +import AbstractSource from "../../sources/AbstractSource.js"; +import { Notifiers } from "../../notifier/Notifiers.js"; + +chai.use(asPromised); + +const emitter = new EventEmitter(); + +// Test source that supports getRecentlyPlayed +class TestTransferSource extends AbstractSource { + testPlays: PlayObject[] = []; + + constructor(name: string, plays: PlayObject[] = []) { + super('test', name, {}, { localUrl: new URL('https://example.com'), configDir: 'fake', logger: loggerTest, version: 'test' }, emitter); + this.testPlays = plays; + this.canBacklog = false; + // Mark as ready + this.buildOK = true; + this.connectionOK = true; + this.requiresAuth = false; + } + + protected async doCheckConnection(): Promise { + return true; + } + + async getRecentlyPlayed(options: { limit?: number } = {}): Promise { + const { limit = 200 } = options; + return this.testPlays.slice(0, limit); + } +} + +// Test client that supports time-range fetching +class TestTransferClient extends AbstractScrobbleClient { + testScrobbles: PlayObject[] = []; + scrobbledPlays: PlayObject[] = []; + + constructor(name: string, supportsTimeRange: boolean = true) { + super('test', name, {}, new Notifiers(emitter, new EventEmitter(), new EventEmitter(), loggerTest), emitter, loggerTest); + this.requiresAuth = false; + this.requiresAuthInteraction = false; + // Mark as ready + this.buildOK = true; + this.connectionOK = true; + + if (supportsTimeRange) { + // Add the method to support time-range fetching + (this as any).getScrobblesForTimeRange = async (fromDate: any, toDate: any, limit: number) => { + return this.testScrobbles.filter(s => { + const playDate = s.data.playDate; + if (!playDate) return false; + return playDate.isAfter(fromDate) && playDate.isBefore(toDate); + }); + }; + } + } + + protected async doCheckConnection(): Promise { + return true; + } + + protected async doBuildInitData(): Promise { + return true; + } + + async doScrobble(playObj: PlayObject): Promise { + this.scrobbledPlays.push(playObj); + return playObj; + } + + async getRecentlyPlayed(limit: number): Promise { + return this.testScrobbles.slice(0, limit); + } + + protected async getScrobblesForRefresh(limit: number): Promise { + // Return testScrobbles for duplicate detection + return this.testScrobbles; + } + + protected async doParseCache() { + await this.cache.init(); + return super.doParseCache(); + } +} + +describe('TransferManager', function() { + + describe('Validation', function() { + + let transferManager: TransferManager; + let sources: ScrobbleSources; + let clients: ScrobbleClients; + + beforeEach(async function() { + sources = new ScrobbleSources(emitter, { + localUrl: new URL('https://example.com'), + configDir: 'fake', + version: 'test' + }, loggerTest); + clients = new ScrobbleClients(emitter, new EventEmitter(), new URL('https://example.com'), 'fake', loggerTest); + transferManager = new TransferManager(sources, clients, loggerTest); + + const source = new TestTransferSource('testSource'); + sources.sources.push(source); + + const client = new TestTransferClient('testClient', true); + await client.initialize(); + clients.clients.push(client); + }); + + it('Rejects future fromDate', async function() { + await assert.isRejected( + transferManager.startTransfer({ + sourceName: 'testSource', + clientName: 'testClient', + fromDate: dayjs().add(1, 'day').format('YYYY-MM-DD') + }), + /fromDate cannot be in the future/ + ); + }); + + it('Rejects future toDate', async function() { + await assert.isRejected( + transferManager.startTransfer({ + sourceName: 'testSource', + clientName: 'testClient', + toDate: dayjs().add(1, 'day').format('YYYY-MM-DD') + }), + /toDate cannot be in the future/ + ); + }); + + it('Rejects backwards date range (fromDate > toDate)', async function() { + await assert.isRejected( + transferManager.startTransfer({ + sourceName: 'testSource', + clientName: 'testClient', + fromDate: '2025-01-15', + toDate: '2025-01-10' + }), + /fromDate must be before toDate/ + ); + }); + + it('Rejects client without time-range support', async function() { + const unsupportedClient = new TestTransferClient('unsupportedClient', false); + await unsupportedClient.initialize(); + clients.clients.push(unsupportedClient); + + await assert.isRejected( + transferManager.startTransfer({ + sourceName: 'testSource', + clientName: 'unsupportedClient', + playCount: 10 + }), + /does not support time-range fetching/ + ); + }); + + it('Accepts valid transfer options with playCount', async function() { + const transferId = await transferManager.startTransfer({ + sourceName: 'testSource', + clientName: 'testClient', + playCount: 10 + }); + + assert.isString(transferId); + assert.isNotEmpty(transferId); + }); + + it('Accepts valid transfer options with date range', async function() { + const transferId = await transferManager.startTransfer({ + sourceName: 'testSource', + clientName: 'testClient', + fromDate: '2025-01-01', + toDate: '2025-01-10' + }); + + assert.isString(transferId); + assert.isNotEmpty(transferId); + }); + }); + +}); + + From 650e202f69cce55ca091e25e04eefc8c2e01227a Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 19 Nov 2025 20:24:46 +0000 Subject: [PATCH 6/6] refactor(transfer): Use interfaces for fetching scrobbles from sources Rather than implementing a function per source, use interface that is implemented by Source to fetch with standard options --- src/backend/common/infrastructure/Atomic.ts | 54 +++++- .../common/vendor/koito/KoitoApiClient.ts | 50 +++++- .../common/vendor/maloja/MalojaApiClient.ts | 24 ++- src/backend/index.ts | 2 + src/backend/sources/KoitoSource.ts | 16 +- src/backend/sources/LastfmSource.ts | 37 +++- src/backend/sources/ListenbrainzSource.ts | 18 +- src/backend/sources/MalojaSource.ts | 13 +- src/backend/transfer/TransferJob.ts | 158 +++++++++++++++++- 9 files changed, 347 insertions(+), 25 deletions(-) diff --git a/src/backend/common/infrastructure/Atomic.ts b/src/backend/common/infrastructure/Atomic.ts index 7c55abab..ade0a448 100644 --- a/src/backend/common/infrastructure/Atomic.ts +++ b/src/backend/common/infrastructure/Atomic.ts @@ -1,6 +1,6 @@ import { Logger } from '@foxxmd/logging'; import { SearchAndReplaceRegExp } from "@foxxmd/regex-buddy-core"; -import { Dayjs } from "dayjs"; +import { Dayjs, ManipulateType } from "dayjs"; import { Request, Response } from "express"; import { NextFunction, ParamsDictionary, Query } from "express-serve-static-core"; import { FixedSizeList } from 'fixed-size-list'; @@ -385,3 +385,55 @@ export interface CacheConfigOptions { auth?: CacheAuthConfig; } +export interface PaginatedLimit { + /** per page max number of results to return */ + limit?: number +} + +export interface PaginatedTimeRangeOptions { + /** Unix timestamp */ + from: number + /** Unix timestamp */ + to: number +} + +export interface PaginatedListensOptions extends PaginatedLimit { + page: number +} + +export interface PagelessListensTimeRangeOptions extends Partial, PaginatedLimit { +} + +export interface PaginatedListensTimeRangeOptions extends Partial, PaginatedListensOptions { +} + +export interface PaginatedResults { + total?: number +} + +export interface PaginatedListens { + getPaginatedListens(params: PaginatedListensOptions): Promise<{data: PlayObject[], meta: PaginatedListensOptions & PaginatedResults}> +} + +export const hasPaginagedListens = (obj: Object): obj is PaginatedListens => { + return 'getPaginatedListens' in obj; +} + +export interface PaginatedTimeRangeListens { + getPaginatedTimeRangeListens(params: PaginatedListensTimeRangeOptions): Promise<{data: PlayObject[], meta: PaginatedListensTimeRangeOptions & PaginatedResults}> + getPaginatedUnitOfTime(): ManipulateType; +} + +export const hasPaginatedTimeRangeListens = (obj: Object): obj is PaginatedTimeRangeListens => { + return 'getPaginatedTimeRangeListens' in obj; +} + +export interface PagelessTimeRangeListens { + getPagelessTimeRangeListens(params: PagelessListensTimeRangeOptions): Promise<{data: PlayObject[], meta: PagelessListensTimeRangeOptions & PaginatedResults}> +} + +export const hasPagelessTimeRangeListens = (obj: Object): obj is PagelessTimeRangeListens => { + return 'getPaginatedTimeRangeListens' in obj; +} + +export type PaginatedSource = PaginatedListens | PaginatedTimeRangeListens | PagelessTimeRangeListens; \ No newline at end of file diff --git a/src/backend/common/vendor/koito/KoitoApiClient.ts b/src/backend/common/vendor/koito/KoitoApiClient.ts index 97ceb696..5b782825 100644 --- a/src/backend/common/vendor/koito/KoitoApiClient.ts +++ b/src/backend/common/vendor/koito/KoitoApiClient.ts @@ -1,6 +1,6 @@ import dayjs from "dayjs"; import { PlayObject, URLData } from "../../../../core/Atomic.js"; -import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER } from "../../infrastructure/Atomic.js"; +import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, PaginatedListensTimeRangeOptions, PaginatedTimeRangeListens } from "../../infrastructure/Atomic.js"; import { KoitoData, ListenObjectResponse, ListensResponse } from "../../infrastructure/config/client/koito.js"; import AbstractApiClient from "../AbstractApiClient.js"; import { getBaseFromUrl, isPortReachableConnect, joinedUrl, normalizeWebAddress } from "../../../utils/NetworkUtils.js"; @@ -18,7 +18,7 @@ interface SubmitOptions { const KOITO_LZ_PATH: RegExp = new RegExp(/^\/apis\/listenbrainz(\/?1?\/?)?$/); -export class KoitoApiClient extends AbstractApiClient { +export class KoitoApiClient extends AbstractApiClient implements PaginatedTimeRangeListens { declare config: KoitoData; url: URLData; @@ -175,6 +175,52 @@ export class KoitoApiClient extends AbstractApiClient { } } + getPaginatedTimeRangeListens = async (params: PaginatedListensTimeRangeOptions) => { + let dateData: {week?: number, month?: number, year?: number} = {}; + if(params.from !== undefined && params.to !== undefined) { + const from = dayjs.unix(params.from); + const to = dayjs.unix(params.to); + if(from.week() === to.week()) { + from.subtract + dateData = { + year: from.year(), + month: from.month(), + week: from.week() + } + } else if(from.month() === to.month()) { + dateData = { + year: from.year(), + month: from.month(), + } + } else { + dateData = { + year: from.year() + } + } + } + const resp = await this.callApi(request + .get(`${joinedUrl(this.url.url, '/apis/web/v1/listens')}`) + .query({ + page: params.page, + limit: params.limit, + ...dateData + })); + + const r = resp.body as ListensResponse; + + return { + data: r.items.map((x => listenObjectResponseToPlay(x))), + meta: { + ...params, + total: r.total_record_count + } + } + } + + getPaginatedUnitOfTime(): dayjs.ManipulateType { + return 'week'; + } + getRecentlyPlayed = async (maxTracks: number): Promise => { try { const resp = await this.getUserListens(maxTracks); diff --git a/src/backend/common/vendor/maloja/MalojaApiClient.ts b/src/backend/common/vendor/maloja/MalojaApiClient.ts index 87aa27bc..389d108f 100644 --- a/src/backend/common/vendor/maloja/MalojaApiClient.ts +++ b/src/backend/common/vendor/maloja/MalojaApiClient.ts @@ -1,11 +1,11 @@ -import dayjs from 'dayjs'; +import dayjs, { ManipulateType } from 'dayjs'; import request, { SuperAgentRequest, Response } from 'superagent'; import compareVersions from "compare-versions"; import AbstractApiClient from "../AbstractApiClient.js"; import { getBaseFromUrl, isPortReachableConnect, joinedUrl, normalizeWebAddress } from "../../../utils/NetworkUtils.js"; import { MalojaData } from "../../infrastructure/config/client/maloja.js"; import { PlayObject, URLData } from "../../../../core/Atomic.js"; -import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../../infrastructure/Atomic.js"; +import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, PaginatedListensTimeRangeOptions, PaginatedTimeRangeListens } from "../../infrastructure/Atomic.js"; import { isNodeNetworkException } from "../../errors/NodeErrors.js"; import { isSuperAgentResponseError } from "../../errors/ErrorUtils.js"; import { getNonEmptyVal, parseRetryAfterSecsFromObj, removeUndefinedKeys, sleep } from "../../../utils.js"; @@ -16,7 +16,7 @@ import { buildTrackString } from '../../../../core/StringUtils.js'; -export class MalojaApiClient extends AbstractApiClient { +export class MalojaApiClient extends AbstractApiClient implements PaginatedTimeRangeListens { declare config: MalojaData; url: URLData; @@ -187,6 +187,24 @@ export class MalojaApiClient extends AbstractApiClient { return list.map(formatPlayObj); } + getPaginatedTimeRangeListens = async (params: PaginatedListensTimeRangeOptions) => { + const resp = await this.callApi(request.get(`${this.url.url}/apis/mlj_1/scrobbles`).query({ + perpage: params.limit, + page: params.page, + from: params.from !== undefined ? dayjs.unix(params.from).format('YYYY/MM/DD') : undefined, + to: params.to !== undefined ? dayjs.unix(params.from).format('YYYY/MM/DD') : undefined + })); + + return { + data: resp.body.list.map(formatPlayObj), + meta: params + } + } + + getPaginatedUnitOfTime(): ManipulateType { + return 'day'; + } + scrobble = async (playObj: PlayObject): Promise<[(MalojaScrobbleData | undefined), MalojaScrobbleV3ResponseData, string?]> => { const { diff --git a/src/backend/index.ts b/src/backend/index.ts index 5049d34a..bea0480d 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -7,6 +7,7 @@ import relativeTime from 'dayjs/plugin/relativeTime.js'; import isToday from 'dayjs/plugin/isToday.js'; import timezone from 'dayjs/plugin/timezone.js'; import utc from 'dayjs/plugin/utc.js'; +import week from 'dayjs/plugin/weekOfYear.js'; import * as path from "path"; import { SimpleIntervalJob, ToadScheduler } from "toad-scheduler"; import { projectDir } from "./common/index.js"; @@ -29,6 +30,7 @@ dayjs.extend(relativeTime); dayjs.extend(duration); dayjs.extend(timezone); dayjs.extend(isToday); +dayjs.extend(week); // eslint-disable-next-line prefer-arrow-functions/prefer-arrow-functions (async function () { diff --git a/src/backend/sources/KoitoSource.ts b/src/backend/sources/KoitoSource.ts index 0692f923..756216c1 100644 --- a/src/backend/sources/KoitoSource.ts +++ b/src/backend/sources/KoitoSource.ts @@ -1,17 +1,13 @@ import EventEmitter from "events"; -import request from "superagent"; import { PlayObject, SOURCE_SOT } from "../../core/Atomic.js"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; -import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz.js"; -import { ListenbrainzApiClient } from "../common/vendor/ListenbrainzApiClient.js"; +import { FormatPlayObjectOptions, InternalConfig, PaginatedListensTimeRangeOptions, PaginatedTimeRangeListens } from "../common/infrastructure/Atomic.js"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; import MemorySource from "./MemorySource.js"; -import { isPortReachableConnect } from "../utils/NetworkUtils.js"; import { KoitoApiClient, listenObjectResponseToPlay } from "../common/vendor/koito/KoitoApiClient.js"; import { KoitoSourceConfig } from "../common/infrastructure/config/source/koito.js"; -export default class KoitoSource extends MemorySource { +export default class KoitoSource extends MemorySource implements PaginatedTimeRangeListens { api: KoitoApiClient; requiresAuth = true; @@ -73,5 +69,13 @@ export default class KoitoSource extends MemorySource { } } + getPaginatedTimeRangeListens = async (params: PaginatedListensTimeRangeOptions) => { + return await this.api.getPaginatedTimeRangeListens(params); + } + + getPaginatedUnitOfTime() { + return this.api.getPaginatedUnitOfTime(); + } + protected getBackloggedPlays = async (options: RecentlyPlayedOptions = {}) => await this.getRecentlyPlayed({formatted: true, ...options}) } diff --git a/src/backend/sources/LastfmSource.ts b/src/backend/sources/LastfmSource.ts index 4bc7aadf..92d9ecd5 100644 --- a/src/backend/sources/LastfmSource.ts +++ b/src/backend/sources/LastfmSource.ts @@ -1,10 +1,10 @@ -import dayjs from "dayjs"; +import dayjs, { ManipulateType } from "dayjs"; import EventEmitter from "events"; import { TrackObject, UserGetRecentTracksResponse } from "lastfm-node-client"; import request from "superagent"; import { PlayObject, SOURCE_SOT } from "../../core/Atomic.js"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import { FormatPlayObjectOptions, InternalConfig, PlayPlatformId } from "../common/infrastructure/Atomic.js"; +import { FormatPlayObjectOptions, InternalConfig, PaginatedListensTimeRangeOptions, PaginatedTimeRangeListens, PlayPlatformId } from "../common/infrastructure/Atomic.js"; import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; import LastfmApiClient from "../common/vendor/LastfmApiClient.js"; import { sortByOldestPlayDate } from "../utils.js"; @@ -14,7 +14,7 @@ import { Logger } from "@foxxmd/logging"; import { PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; import { NowPlayingPlayerState } from "./PlayerState/NowPlayingPlayerState.js"; -export default class LastfmSource extends MemorySource { +export default class LastfmSource extends MemorySource implements PaginatedTimeRangeListens { api: LastfmApiClient; requiresAuth = true; @@ -156,6 +156,37 @@ export default class LastfmSource extends MemorySource { } } + getPaginatedTimeRangeListens = async (params: PaginatedListensTimeRangeOptions) => { + const resp = await this.api.getRecentTracksWithPagination({ + page: params.page, + to: params.to, + from: params.from, + limit: params.limit + }); + + const { + recenttracks: { + track: rawTracks = [], + '@attr': pageInfo + } + } = resp; + + return { + data: rawTracks + .filter(t => t.date !== undefined) + .map(t => LastfmApiClient.formatPlayObj(t)), + meta: { + ...params, + total: parseInt(pageInfo.total) + } + } + + } + + getPaginatedUnitOfTime(): ManipulateType { + return 'second'; + } + protected getBackloggedPlays = async (options: RecentlyPlayedOptions = {}) => await this.getRecentlyPlayed({formatted: true, ...options}) getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => new NowPlayingPlayerState(logger, id, opts); diff --git a/src/backend/sources/ListenbrainzSource.ts b/src/backend/sources/ListenbrainzSource.ts index 68def67f..4fe9a986 100644 --- a/src/backend/sources/ListenbrainzSource.ts +++ b/src/backend/sources/ListenbrainzSource.ts @@ -2,7 +2,7 @@ import EventEmitter from "events"; import request from "superagent"; import { PlayObject, SOURCE_SOT } from "../../core/Atomic.js"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import { FormatPlayObjectOptions, InternalConfig, PlayPlatformId } from "../common/infrastructure/Atomic.js"; +import { FormatPlayObjectOptions, InternalConfig, PagelessListensTimeRangeOptions, PagelessTimeRangeListens, PlayPlatformId } from "../common/infrastructure/Atomic.js"; import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz.js"; import { ListenbrainzApiClient } from "../common/vendor/ListenbrainzApiClient.js"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; @@ -11,8 +11,9 @@ import { isPortReachableConnect } from "../utils/NetworkUtils.js"; import { Logger } from "@foxxmd/logging"; import { PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; import { NowPlayingPlayerState } from "./PlayerState/NowPlayingPlayerState.js"; +import { ManipulateType } from "dayjs"; -export default class ListenbrainzSource extends MemorySource { +export default class ListenbrainzSource extends MemorySource implements PagelessTimeRangeListens { api: ListenbrainzApiClient; requiresAuth = true; @@ -91,6 +92,19 @@ export default class ListenbrainzSource extends MemorySource { } } + getPagelessTimeRangeListens = async (options: PagelessListensTimeRangeOptions) => { + const resp = await this.api.getUserListensWithPagination({ + count: options.limit, + minTs: options.from, + maxTs: options.to + }); + return {data: resp.listens.map(x => ListenbrainzSource.formatPlayObj(x)), meta: {...options, total: resp.count}}; + } + + getPaginatedUnitOfTime(): ManipulateType { + return 'second'; + } + protected getBackloggedPlays = async (options: RecentlyPlayedOptions = {}) => await this.getRecentlyPlayed({formatted: true, ...options}) getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => new NowPlayingPlayerState(logger, id, opts); diff --git a/src/backend/sources/MalojaSource.ts b/src/backend/sources/MalojaSource.ts index bab09c93..f6456a03 100644 --- a/src/backend/sources/MalojaSource.ts +++ b/src/backend/sources/MalojaSource.ts @@ -1,13 +1,13 @@ import EventEmitter from "events"; import { PlayObject, SOURCE_SOT } from "../../core/Atomic.js"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import { InternalConfig } from "../common/infrastructure/Atomic.js"; +import { InternalConfig, PaginatedListensTimeRangeOptions, PaginatedTimeRangeListens } from "../common/infrastructure/Atomic.js"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; import MemorySource from "./MemorySource.js"; import { MalojaApiClient } from "../common/vendor/maloja/MalojaApiClient.js"; import { MalojaSourceConfig } from "../common/infrastructure/config/source/maloja.js"; -export default class MalojaSource extends MemorySource { +export default class MalojaSource extends MemorySource implements PaginatedTimeRangeListens { api: MalojaApiClient; requiresAuth = true; @@ -67,6 +67,15 @@ export default class MalojaSource extends MemorySource { } } + getPaginatedTimeRangeListens = async (params: PaginatedListensTimeRangeOptions) => { + return await this.api.getPaginatedTimeRangeListens(params); + } + + getPaginatedUnitOfTime() { + return this.api.getPaginatedUnitOfTime(); + } + + protected getBackloggedPlays = async (options: RecentlyPlayedOptions = {}) => await this.getRecentlyPlayed({ formatted: true, ...options }) } \ No newline at end of file diff --git a/src/backend/transfer/TransferJob.ts b/src/backend/transfer/TransferJob.ts index 46a450f5..ac35712a 100644 --- a/src/backend/transfer/TransferJob.ts +++ b/src/backend/transfer/TransferJob.ts @@ -2,7 +2,7 @@ import { childLogger, Logger } from "@foxxmd/logging"; import dayjs, { Dayjs } from "dayjs"; import { PlayObject } from "../../core/Atomic.js"; import { buildTrackString } from "../../core/StringUtils.js"; -import { TRANSFORM_HOOK } from "../common/infrastructure/Atomic.js"; +import { hasPagelessTimeRangeListens, hasPaginagedListens, hasPaginatedTimeRangeListens, PaginatedSource, TRANSFORM_HOOK } from "../common/infrastructure/Atomic.js"; import LastfmApiClient from "../common/vendor/LastfmApiClient.js"; import { ListenbrainzApiClient } from "../common/vendor/ListenbrainzApiClient.js"; import AbstractScrobbleClient from "../scrobblers/AbstractScrobbleClient.js"; @@ -139,12 +139,10 @@ export class TransferJob { throw new Error(`Client '${this.clientName}' is not ready`); } - if (source instanceof LastfmSource) { - await this.runLastfmTransfer(source as LastfmSource, client); - } else if (source instanceof ListenbrainzSource) { - await this.runListenbrainzTransfer(source as ListenbrainzSource, client); + if(this.playCount !== undefined) { + await this.runPlayCountTransfer(source as unknown as PaginatedSource, client); } else { - await this.runGenericTransfer(source, client); + await this.runTimeRangeTransfer(source as unknown as PaginatedSource, client); } this.progress.status = 'completed'; @@ -304,6 +302,154 @@ export class TransferJob { await this.processPlaysWithSlidingWindow(sortedPlays, client); } + private async runPlayCountTransfer(source: PaginatedSource, client: AbstractScrobbleClient): Promise { + + let currentPage = 1; + let hasMorePages = true; + let totalPages: number | undefined; + let totalPagesKnown = false; + let pageSize = this.PAGE_SIZE; + + let fromDate: number | undefined; + let toDate: number | undefined; + + while(hasMorePages) { + this.progress.currentPage = currentPage; + + const remaining = this.playCount - this.progress.processed; + pageSize = Math.min(this.PAGE_SIZE, remaining); + if (pageSize <= 0) { + this.logger.info(`Reached play count limit of ${this.playCount}`); + break; + } + + + let plays: PlayObject[] = []; + + if(hasPagelessTimeRangeListens(source)) { + const resp = await source.getPagelessTimeRangeListens({ + from: fromDate, + to: toDate, + limit: pageSize + }); + if(resp.meta.total !== undefined) { + totalPages = resp.meta.total; + } + if(resp.data.length === 0) { + hasMorePages = false; + } + plays = [...resp.data]; + plays.sort(sortByOldestPlayDate); + toDate = plays[0].data.playDate.unix() - 1; + } else if(hasPaginagedListens(source)) { + + const resp = await source.getPaginatedListens({ + page: currentPage, + limit: pageSize + }); + if(resp.meta.total !== undefined) { + totalPages = resp.meta.total; + } + plays = [...resp.data]; + plays.sort(sortByOldestPlayDate); + } else { + throw new Error('Source does not support recent listens without a time range'); + } + + if(!totalPagesKnown && totalPages !== undefined) { + this.progress.total = this.playCount ? Math.min(this.playCount, totalPages) : totalPages; + this.progress.totalPages = Math.ceil(this.progress.total / this.PAGE_SIZE); + + totalPagesKnown = true; + this.logger.info(`Total plays in source: ${totalPages}, Will transfer: ${this.progress.total}, Expected pages: ${this.progress.totalPages}`); + } + + if(!hasMorePages) { + break; + } + + if (plays.length > 0) { + await this.processPlaysWithSlidingWindow(plays, client); + } + + if (this.playCount && this.progress.processed >= this.playCount) { + this.logger.info(`Reached play count limit of ${this.playCount}`); + hasMorePages = false; + } else { + currentPage++; + } + } + } + + + private async runTimeRangeTransfer(source: PaginatedSource, client: AbstractScrobbleClient): Promise { + + let currentPage = 1; + let hasMorePages = true; + let totalPages: number | undefined; + let totalPagesKnown = false; + let pageSize = this.PAGE_SIZE; + + let fromDate: number | undefined = this.fromDate.unix(); + let toDate: number | undefined = this.toDate.unix(); + + while(hasMorePages) { + this.progress.currentPage = currentPage; + + let plays: PlayObject[] = []; + + if(hasPagelessTimeRangeListens(source)) { + const resp = await source.getPagelessTimeRangeListens({ + from: fromDate, + to: toDate, + limit: pageSize + }); + if(resp.meta.total !== undefined) { + totalPages = resp.meta.total; + } + if(resp.data.length === 0) { + hasMorePages = false; + } + plays = [...resp.data]; + plays.sort(sortByOldestPlayDate); + fromDate = plays[plays.length - 1].data.playDate.unix() + 1; + } else if(hasPaginatedTimeRangeListens(source)) { + + const resp = await source.getPaginatedTimeRangeListens({ + page: currentPage, + from: fromDate, + to: toDate, + limit: pageSize + }); + if(resp.meta.total !== undefined) { + totalPages = resp.meta.total; + } + plays = [...resp.data]; + plays.sort(sortByOldestPlayDate); + } else { + throw new Error('Source does not support time ranges'); + } + + if(!totalPagesKnown && totalPages !== undefined) { + this.progress.total = this.playCount ? Math.min(this.playCount, totalPages) : totalPages; + this.progress.totalPages = Math.ceil(this.progress.total / this.PAGE_SIZE); + + totalPagesKnown = true; + this.logger.info(`Total plays in source: ${totalPages}, Will transfer: ${this.progress.total}, Expected pages: ${this.progress.totalPages}`); + } + + if(!hasMorePages) { + break; + } + + if (plays.length > 0) { + await this.processPlaysWithSlidingWindow(plays, client); + } + + currentPage++; + } + } + private timeRangeScrobbles: PlayObject[] = []; private async processPlaysWithSlidingWindow(plays: PlayObject[], client: AbstractScrobbleClient): Promise {