From 4e45d35925f11a70fb11fa8e221acb37725e7eaf Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 8 Feb 2024 11:47:58 -0500 Subject: [PATCH 01/34] test: Add test that handles no existing scrobble match with score greater than 0 #130 Detects track string error thrown reported via https://github.com/FoxxMD/multi-scrobbler/issues/130#issuecomment-1934452275 --- .../tests/scrobbler/scrobblers.test.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/backend/tests/scrobbler/scrobblers.test.ts b/src/backend/tests/scrobbler/scrobblers.test.ts index 38ebddbd..29bab612 100644 --- a/src/backend/tests/scrobbler/scrobblers.test.ts +++ b/src/backend/tests/scrobbler/scrobblers.test.ts @@ -1,9 +1,12 @@ import {describe, it, after, before} from 'mocha'; -import {assert} from 'chai'; +import chai, {assert} from 'chai'; +import asPromised from 'chai-as-promised'; import clone from 'clone'; import pEvent from 'p-event'; import { http, HttpResponse } from 'msw'; +chai.use(asPromised); + import withDuration from '../plays/withDuration.json'; import mixedDuration from '../plays/mixedDuration.json'; @@ -111,6 +114,23 @@ describe('Detects duplicate and unique scrobbles from client recent history', fu assert.isFalse(await testScrobbler.alreadyScrobbled(newScrobble)); }); + + it('It handles unique detection when no existing scrobble matches above a score of 0', async function () { + + testScrobbler.recentScrobbles = normalizedWithMixedDur; + + const uniquePlay = generatePlay({ + artists: [ + "2814" + ], + track: "新宿ゴールデン街", + duration: 130, + playDate: normalizedWithMixedDur[normalizedWithMixedDur.length - 3].data.playDate.add(6, 'minutes') + }); + + await assert.isFulfilled( testScrobbler.alreadyScrobbled(uniquePlay)) + await assert.eventually.isFalse(testScrobbler.alreadyScrobbled(uniquePlay)) + }); }); describe('When scrobble track/artist/album matches existing but is a new scrobble', function () { -- 2.51.2 From be9675bb100bdbe3f53dbc156adcf829f0c39196 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 8 Feb 2024 11:51:00 -0500 Subject: [PATCH 02/34] fix: Handle when no existing scrobble match with score greater than 0 #130 https://github.com/FoxxMD/multi-scrobbler/issues/130#issuecomment-1934452275 --- src/backend/scrobblers/AbstractScrobbleClient.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index 565c4e78..a31dbf7a 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -310,7 +310,7 @@ export default abstract class AbstractScrobbleClient implements Authenticatable } let existingScrobble; - let closestMatch: {score: number, breakdowns: string[], confidence: string, scrobble?: PlayObject} = {score: 0, breakdowns: [], confidence: 'None'}; + let closestMatch: {score: number, breakdowns: string[], confidence: string, scrobble?: PlayObject} = {score: 0, breakdowns: [], confidence: 'No existing scrobble matched with a score higher than 0'}; // then check if we have already recorded this const [existingExactSubmitted, existingDataSubmitted = []] = this.findExistingSubmittedPlayObj(playObj); @@ -419,9 +419,13 @@ export default abstract class AbstractScrobbleClient implements Authenticatable } if ((existingScrobble !== undefined && this.verboseOptions.match.onMatch) || (existingScrobble === undefined && this.verboseOptions.match.onNoMatch)) { - const closestScrobble = `Closest Scrobble: ${buildTrackString(closestMatch.scrobble, scoreTrackOpts)} => ${closestMatch.confidence}`; - this.logger.debug(`${capitalize(playObj.meta.source ?? 'Source')}: ${buildTrackString(playObj, scoreTrackOpts)} => ${closestScrobble}`, {leaf: ['Dupe Check']}); - if (this.verboseOptions.match.confidenceBreakdown === true) { + const closestScrobbleParts: string[] = []; + if(closestMatch.scrobble !== undefined) { + closestScrobbleParts.push(`Closest Scrobble: ${buildTrackString(closestMatch.scrobble, scoreTrackOpts)}`); + } + closestScrobbleParts.push(closestMatch.confidence); + this.logger.debug(`${capitalize(playObj.meta.source ?? 'Source')}: ${buildTrackString(playObj, scoreTrackOpts)} => ${closestScrobbleParts.join(' => ')}`, {leaf: ['Dupe Check']}); + if (this.verboseOptions.match.confidenceBreakdown === true && closestMatch.breakdowns.length > 0) { this.logger.debug(`Breakdown: ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); } -- 2.51.2 From 0efbe41b7e9960bb651a6002015d523f7332db61 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 8 Feb 2024 12:14:11 -0500 Subject: [PATCH 03/34] test: Use tsx through mocha config --- .mocharc.json | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.mocharc.json b/.mocharc.json index b6d3f783..b111ee1a 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,3 +1,4 @@ { - "reporter": "dot" + "reporter": "dot", + "require": ["tsx"] } diff --git a/package.json b/package.json index 6fe41ace..6b67291b 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "typedoc": "typedoc", "circular": "madge --circular --extensions ts src/index.ts", "test": "npm run -s test:backend", - "test:backend": "NODE_OPTIONS='--loader tsx' mocha --extension ts --reporter spec --recursive src/backend/tests/**/*.test.ts", + "test:backend": "mocha --extension ts --reporter spec --recursive src/backend/tests/**/*.test.ts", "fileEndings": "jscodeshift --transformFrom js --transformTo none --importTypes relative --extensions=ts --parser tsx --transform codeshift/transform.ts src/backend", "dev": "APP_VERSION=$npm_package_version nodemon -w src/backend -x tsx src/backend/index.ts", "start": "APP_VERSION=$npm_package_version NODE_ENV=production tsx src/backend/index.ts", -- 2.51.2 From f231d4e1e40f3f112f7bf02006f5447b23e388a0 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 8 Feb 2024 13:07:13 -0500 Subject: [PATCH 04/34] ci: Implement test checks for PRs and image publishing --- .github/workflows/publishImage.yml | 21 ++++++++++++++++++++- .github/workflows/test.yml | 24 ++++++++++++++++++++++++ package.json | 4 +++- 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/publishImage.yml b/.github/workflows/publishImage.yml index 1405079c..4e12182c 100644 --- a/.github/workflows/publishImage.yml +++ b/.github/workflows/publishImage.yml @@ -12,9 +12,28 @@ on: - '**.md' jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: '18.x' + cache: 'npm' + - name: Install dev dependencies + run: npm ci + - name: Build Backend + run: 'npm run build:backend' + - name: Test Backend + run: npm run test + push_to_registry: - name: Push Docker image to Docker Hub + name: Build and push container images runs-on: ubuntu-latest + needs: test strategy: fail-fast: false matrix: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..3a957939 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: Publish Docker image to Dockerhub + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: '18.x' + cache: 'npm' + - name: Install dev dependencies + run: npm ci + - name: Build Backend + run: 'npm run build:backend' + - name: Test Backend + run: npm run test diff --git a/package.json b/package.json index 6b67291b..2a72f316 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,9 @@ "fileEndings": "jscodeshift --transformFrom js --transformTo none --importTypes relative --extensions=ts --parser tsx --transform codeshift/transform.ts src/backend", "dev": "APP_VERSION=$npm_package_version nodemon -w src/backend -x tsx src/backend/index.ts", "start": "APP_VERSION=$npm_package_version NODE_ENV=production tsx src/backend/index.ts", - "build": "APP_VERSION=$npm_package_version vite build", + "build:frontend": "APP_VERSION=$npm_package_version vite build", + "build:backend": "tsc -p src/backend", + "build": "npm run -s build:backend && npm run -s build:frontend", "postinstall": "patch-package" }, "exports": { -- 2.51.2 From 41c696992a4a4102047105ddcb32ac975966bc5f Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Tue, 13 Feb 2024 15:59:17 -0500 Subject: [PATCH 05/34] feat: Add legacy fallback options for subsonic communication #136 * Add config options for using legacy authentication and ignoring TLS cert errors * Fallback to legacy auth if subsonic error code 41 is detected --- .../infrastructure/config/source/subsonic.ts | 18 + src/backend/common/schema/aio-client.json | 134 +- src/backend/common/schema/aio-source.json | 840 +++--- src/backend/common/schema/aio.json | 2460 +++++++++-------- src/backend/common/schema/client.json | 122 +- src/backend/common/schema/source.json | 756 ++--- src/backend/sources/SubsonicSource.ts | 48 +- 7 files changed, 2232 insertions(+), 2146 deletions(-) diff --git a/src/backend/common/infrastructure/config/source/subsonic.ts b/src/backend/common/infrastructure/config/source/subsonic.ts index 5e2ac53b..6a34c959 100644 --- a/src/backend/common/infrastructure/config/source/subsonic.ts +++ b/src/backend/common/infrastructure/config/source/subsonic.ts @@ -37,6 +37,24 @@ export interface SubsonicData extends CommonSourceData, PollingOptions { * @examples [30] * */ maxInterval?: number + + /** + * If your subsonic server is using self-signed certs you may need to disable TLS errors in order to get a connection + * + * WARNING: This should be used with caution as your traffic may not be encrypted. + * + * @default false + * */ + ignoreTlsErrors?: boolean + + /** + * 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 + * + * If 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 + * */ + legacyAuthentication?: boolean } export interface SubSonicSourceConfig extends CommonSourceConfig { data: SubsonicData diff --git a/src/backend/common/schema/aio-client.json b/src/backend/common/schema/aio-client.json index ef66c4fd..a4ecb037 100644 --- a/src/backend/common/schema/aio-client.json +++ b/src/backend/common/schema/aio-client.json @@ -1,21 +1,21 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "ClientAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig": { "anyOf": [ { - "$ref": "#/definitions/LastfmClientAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig" }, { - "$ref": "#/definitions/ListenBrainzClientAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig" }, { - "$ref": "#/definitions/MalojaClientAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig" } ], - "title": "ClientAIOConfig" + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig" }, - "CommonClientData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData": { "properties": { "maxRequestRetries": { "default": 1, @@ -44,7 +44,7 @@ 1 ], "title": "deadLetterRetries", - "type": "boolean" + "type": "number" }, "refreshEnabled": { "default": true, @@ -59,7 +59,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/MatchLoggingOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", "title": "match" } }, @@ -80,10 +80,44 @@ "type": "number" } }, - "title": "CommonClientData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions": { + "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", + "properties": { + "confidenceBreakdown": { + "default": false, + "description": "Include confidence breakdowns in track match logging, if applicable", + "examples": [ + false + ], + "title": "confidenceBreakdown", + "type": "boolean" + }, + "onMatch": { + "default": false, + "description": "Log to DEBUG when a new track DOES match an existing scrobble", + "examples": [ + false + ], + "title": "onMatch", + "type": "boolean" + }, + "onNoMatch": { + "default": false, + "description": "Log to DEBUG when a new track does NOT match an existing scrobble", + "examples": [ + false + ], + "title": "onNoMatch", + "type": "boolean" + } + }, + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", "type": "object" }, - "LastfmClientAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig": { "properties": { "configureAs": { "default": "client", @@ -101,10 +135,10 @@ "data": { "allOf": [ { - "$ref": "#/definitions/CommonClientData" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData" }, { - "$ref": "#/definitions/LastfmData" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData" } ], "description": "Specific data required to configure this client", @@ -140,10 +174,10 @@ "name", "type" ], - "title": "LastfmClientAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig", "type": "object" }, - "LastfmData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData": { "properties": { "apiKey": { "description": "API Key generated from Last.fm account", @@ -198,10 +232,10 @@ "apiKey", "secret" ], - "title": "LastfmData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData", "type": "object" }, - "ListenBrainzClientAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig": { "properties": { "configureAs": { "default": "client", @@ -217,7 +251,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/ListenBrainzClientData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", "description": "Specific data required to configure this client", "title": "data" }, @@ -251,10 +285,10 @@ "name", "type" ], - "title": "ListenBrainzClientAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig", "type": "object" }, - "ListenBrainzClientData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData": { "properties": { "maxRequestRetries": { "default": 1, @@ -283,7 +317,7 @@ 1 ], "title": "deadLetterRetries", - "type": "boolean" + "type": "number" }, "refreshEnabled": { "default": true, @@ -298,7 +332,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/MatchLoggingOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", "title": "match" } }, @@ -345,13 +379,13 @@ "token", "username" ], - "title": "ListenBrainzClientData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", "type": "object" }, - "MalojaClientAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig": { "properties": { "data": { - "$ref": "#/definitions/MalojaClientData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", "description": "Specific data required to configure this client", "title": "data" }, @@ -385,10 +419,10 @@ "name", "type" ], - "title": "MalojaClientAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig", "type": "object" }, - "MalojaClientData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData": { "properties": { "apiKey": { "description": "API Key for Maloja server", @@ -425,7 +459,7 @@ 1 ], "title": "deadLetterRetries", - "type": "boolean" + "type": "number" }, "refreshEnabled": { "default": true, @@ -440,7 +474,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/MatchLoggingOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", "title": "match" } }, @@ -473,44 +507,10 @@ "apiKey", "url" ], - "title": "MalojaClientData", - "type": "object" - }, - "MatchLoggingOptions": { - "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", - "properties": { - "confidenceBreakdown": { - "default": false, - "description": "Include confidence breakdowns in track match logging, if applicable", - "examples": [ - false - ], - "title": "confidenceBreakdown", - "type": "boolean" - }, - "onMatch": { - "default": false, - "description": "Log to DEBUG when a new track DOES match an existing scrobble", - "examples": [ - false - ], - "title": "onMatch", - "type": "boolean" - }, - "onNoMatch": { - "default": false, - "description": "Log to DEBUG when a new track does NOT match an existing scrobble", - "examples": [ - false - ], - "title": "onNoMatch", - "type": "boolean" - } - }, - "title": "MatchLoggingOptions", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", "type": "object" }, - "RequestRetryOptions": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions": { "properties": { "maxRequestRetries": { "default": 1, @@ -531,18 +531,18 @@ "type": "number" } }, - "title": "RequestRetryOptions", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions", "type": "object" } }, "properties": { "clientDefaults": { - "$ref": "#/definitions/RequestRetryOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions", "title": "clientDefaults" }, "clients": { "items": { - "$ref": "#/definitions/ClientAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig" }, "title": "clients", "type": "array" diff --git a/src/backend/common/schema/aio-source.json b/src/backend/common/schema/aio-source.json index cedb02ce..33723b89 100644 --- a/src/backend/common/schema/aio-source.json +++ b/src/backend/common/schema/aio-source.json @@ -1,7 +1,11 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "ChromecastData": { + "Record": { + "title": "Record", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData": { "properties": { "allowUnknownMedia": { "anyOf": [ @@ -64,7 +68,7 @@ "devices": { "description": "A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set", "items": { - "$ref": "#/definitions/ChromecastDeviceInfo" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo" }, "title": "devices", "type": "array" @@ -109,7 +113,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -167,10 +171,10 @@ "title": "whitelistDevices" } }, - "title": "ChromecastData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", "type": "object" }, - "ChromecastDeviceInfo": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo": { "properties": { "address": { "description": "The IP address of the device", @@ -193,10 +197,10 @@ "address", "name" ], - "title": "ChromecastDeviceInfo", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo", "type": "object" }, - "ChromecastSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -213,7 +217,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/ChromecastData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", "title": "data" }, "enable": { @@ -231,7 +235,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -246,57 +250,10 @@ "data", "type" ], - "title": "ChromecastSourceAIOConfig", - "type": "object" - }, - "CommonSourceOptions": { - "properties": { - "logFilterFailure": { - "default": "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.", - "enum": [ - "debug", - false, - "warn" - ], - "examples": [ - "warn" - ], - "title": "logFilterFailure" - }, - "logPayload": { - "default": false, - "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", - "examples": [ - false - ], - "title": "logPayload", - "type": "boolean" - }, - "logPlayerState": { - "default": false, - "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", - "examples": [ - false - ], - "title": "logPlayerState", - "type": "boolean" - }, - "scrobbleBacklog": { - "default": true, - "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", - "examples": [ - true, - false - ], - "title": "scrobbleBacklog", - "type": "boolean" - } - }, - "title": "CommonSourceOptions", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig", "type": "object" }, - "DeezerData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData": { "properties": { "clientId": { "description": "deezer client id", @@ -364,7 +321,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -374,10 +331,10 @@ "clientSecret", "redirectUri" ], - "title": "DeezerData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", "type": "object" }, - "DeezerSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -394,7 +351,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/DeezerData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", "title": "data" }, "enable": { @@ -412,7 +369,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -427,29 +384,82 @@ "data", "type" ], - "title": "DeezerSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig", "type": "object" }, - "JRiverData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions": { "properties": { - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", + "logFilterFailure": { + "default": "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.", + "enum": [ + "debug", + false, + "warn" + ], "examples": [ - 10 + "warn" ], - "title": "interval", - "type": "number" + "title": "logFilterFailure" }, - "maxInterval": { - "default": 30, - "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)", + "logPayload": { + "default": false, + "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", "examples": [ - 30 + false ], - "title": "maxInterval", + "title": "logPayload", + "type": "boolean" + }, + "logPlayerState": { + "default": false, + "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", + "examples": [ + false + ], + "title": "logPlayerState", + "type": "boolean" + }, + "scrobbleBacklog": { + "default": true, + "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", + "examples": [ + true, + false + ], + "title": "scrobbleBacklog", + "type": "boolean" + } + }, + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds": { + "properties": { + "duration": { + "default": 240, + "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", + "examples": [ + 240 + ], + "title": "duration", "type": "number" }, + "percent": { + "default": 50, + "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.", + "examples": [ + 50 + ], + "title": "percent", + "type": "number" + } + }, + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).SourceRetryOptions": { + "properties": { "maxPollRetries": { "default": 5, "description": "default # of automatic polling restarts on error", @@ -468,15 +478,6 @@ "title": "maxRequestRetries", "type": "number" }, - "options": { - "$ref": "#/definitions/Record", - "title": "options" - }, - "password": { - "description": "If you have enabled authentication, the password you set", - "title": "password", - "type": "string" - }, "retryMultiplier": { "default": 1.5, "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", @@ -485,87 +486,12 @@ ], "title": "retryMultiplier", "type": "number" - }, - "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.", - "title": "scrobbleThresholds" - }, - "url": { - "default": "http://localhost:52199/MCWS/v1/", - "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/" - ], - "title": "url", - "type": "string" - }, - "username": { - "description": "If you have enabled authentication, the username you set", - "title": "username", - "type": "string" - } - }, - "required": [ - "url" - ], - "title": "JRiverData", - "type": "object" - }, - "JRiverSourceAIOConfig": { - "properties": { - "clients": { - "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", - "examples": [ - [ - "MyMalojaConfigName", - "MyLastFMConfigName" - ] - ], - "items": { - "type": "string" - }, - "title": "clients", - "type": "array" - }, - "data": { - "$ref": "#/definitions/JRiverData", - "title": "data" - }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", - "examples": [ - true - ], - "title": "enable", - "type": "boolean" - }, - "name": { - "description": "Unique identifier for this source.", - "title": "name", - "type": "string" - }, - "options": { - "$ref": "#/definitions/CommonSourceOptions", - "title": "options" - }, - "type": { - "enum": [ - "jriver" - ], - "title": "type", - "type": "string" } }, - "required": [ - "data", - "type" - ], - "title": "JRiverSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).SourceRetryOptions", "type": "object" }, - "JellyData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData": { "properties": { "maxPollRetries": { "default": 5, @@ -624,7 +550,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -662,18 +588,153 @@ ], "description": "optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled", "examples": [ - [ - "MyUser1", - "MyUser2" - ] + [ + "MyUser1", + "MyUser2" + ] + ], + "title": "users" + } + }, + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig": { + "properties": { + "clients": { + "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", + "examples": [ + [ + "MyMalojaConfigName", + "MyLastFMConfigName" + ] + ], + "items": { + "type": "string" + }, + "title": "clients", + "type": "array" + }, + "data": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", + "examples": [ + true + ], + "title": "enable", + "type": "boolean" + }, + "name": { + "description": "Unique identifier for this source.", + "title": "name", + "type": "string" + }, + "options": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "title": "options" + }, + "type": { + "enum": [ + "jellyfin" + ], + "title": "type", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData": { + "properties": { + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", + "examples": [ + 10 + ], + "title": "interval", + "type": "number" + }, + "maxInterval": { + "default": 30, + "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)", + "examples": [ + 30 + ], + "title": "maxInterval", + "type": "number" + }, + "maxPollRetries": { + "default": 5, + "description": "default # of automatic polling restarts on error", + "examples": [ + 5 + ], + "title": "maxPollRetries", + "type": "number" + }, + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" + }, + "options": { + "$ref": "#/definitions/Record", + "title": "options" + }, + "password": { + "description": "If you have enabled authentication, the password you set", + "title": "password", + "type": "string" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "examples": [ + 1.5 + ], + "title": "retryMultiplier", + "type": "number" + }, + "scrobbleThresholds": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", + "title": "scrobbleThresholds" + }, + "url": { + "default": "http://localhost:52199/MCWS/v1/", + "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/" ], - "title": "users" + "title": "url", + "type": "string" + }, + "username": { + "description": "If you have enabled authentication, the username you set", + "title": "username", + "type": "string" } }, - "title": "JellyData", + "required": [ + "url" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", "type": "object" }, - "JellySourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -690,7 +751,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/JellyData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", "title": "data" }, "enable": { @@ -708,12 +769,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "jellyfin" + "jriver" ], "title": "type", "type": "string" @@ -723,10 +784,10 @@ "data", "type" ], - "title": "JellySourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig", "type": "object" }, - "KodiData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData": { "properties": { "interval": { "default": 10, @@ -783,7 +844,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -807,10 +868,10 @@ "url", "username" ], - "title": "KodiData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", "type": "object" }, - "KodiSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -827,7 +888,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/KodiData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", "title": "data" }, "enable": { @@ -845,7 +906,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -860,10 +921,10 @@ "data", "type" ], - "title": "KodiSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig", "type": "object" }, - "LastFmSouceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -892,7 +953,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/LastFmSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", "title": "data" }, "enable": { @@ -910,7 +971,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -925,10 +986,10 @@ "data", "type" ], - "title": "LastFmSouceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig", "type": "object" }, - "LastFmSourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData": { "properties": { "apiKey": { "description": "API Key generated from Last.fm account", @@ -997,7 +1058,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1019,10 +1080,10 @@ "apiKey", "secret" ], - "title": "LastFmSourceData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", "type": "object" }, - "ListenBrainzSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1051,7 +1112,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/ListenBrainzSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", "title": "data" }, "enable": { @@ -1069,7 +1130,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -1084,10 +1145,10 @@ "data", "type" ], - "title": "ListenBrainzSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig", "type": "object" }, - "ListenBrainzSourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData": { "properties": { "interval": { "default": 10, @@ -1139,7 +1200,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1170,31 +1231,45 @@ "token", "username" ], - "title": "ListenBrainzSourceData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", "type": "object" }, - "MPRISData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData": { "properties": { - "blacklist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } + "albumBlacklist": { + "default": [ + "Soundcloud" ], - "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", + "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": [ [ - "spotify", - "vlc" + "Soundcloud", + "Mixcloud" ] ], - "title": "blacklist" + "items": { + "type": "string" + }, + "title": "albumBlacklist", + "type": "array" + }, + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", + "examples": [ + 10 + ], + "title": "interval", + "type": "number" + }, + "maxInterval": { + "default": 30, + "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)", + "examples": [ + 30 + ], + "title": "maxInterval", + "type": "number" }, "maxPollRetries": { "default": 5, @@ -1228,36 +1303,40 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "whitelist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } - ], - "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", + "uriBlacklist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriBlacklist", + "type": "array" + }, + "uriWhitelist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriWhitelist", + "type": "array" + }, + "url": { + "default": "ws://localhost:6680/mopidy/ws/", + "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": [ - [ - "spotify", - "vlc" - ] + "ws://localhost:6680/mopidy/ws/" ], - "title": "whitelist" + "title": "url", + "type": "string" } }, - "title": "MPRISData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", "type": "object" }, - "MPRISSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1274,7 +1353,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/MPRISData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", "title": "data" }, "enable": { @@ -1292,12 +1371,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "mpris" + "mopidy" ], "title": "type", "type": "string" @@ -1307,45 +1386,31 @@ "data", "type" ], - "title": "MPRISSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig", "type": "object" }, - "MopidyData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData": { "properties": { - "albumBlacklist": { - "default": [ - "Soundcloud" + "blacklist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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.", + "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", "examples": [ [ - "Soundcloud", - "Mixcloud" + "spotify", + "vlc" ] ], - "items": { - "type": "string" - }, - "title": "albumBlacklist", - "type": "array" - }, - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 - ], - "title": "interval", - "type": "number" - }, - "maxInterval": { - "default": 30, - "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)", - "examples": [ - 30 - ], - "title": "maxInterval", - "type": "number" + "title": "blacklist" }, "maxPollRetries": { "default": 5, @@ -1379,40 +1444,36 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "uriBlacklist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriBlacklist", - "type": "array" - }, - "uriWhitelist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriWhitelist", - "type": "array" - }, - "url": { - "default": "ws://localhost:6680/mopidy/ws/", - "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/`", + "whitelist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", "examples": [ - "ws://localhost:6680/mopidy/ws/" + [ + "spotify", + "vlc" + ] ], - "title": "url", - "type": "string" + "title": "whitelist" } }, - "title": "MopidyData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", "type": "object" }, - "MopidySourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1429,7 +1490,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/MopidyData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", "title": "data" }, "enable": { @@ -1447,12 +1508,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "mopidy" + "mpris" ], "title": "type", "type": "string" @@ -1462,10 +1523,10 @@ "data", "type" ], - "title": "MopidySourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig", "type": "object" }, - "PlexSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1482,7 +1543,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/PlexSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", "title": "data" }, "enable": { @@ -1500,7 +1561,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -1515,10 +1576,10 @@ "data", "type" ], - "title": "PlexSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig", "type": "object" }, - "PlexSourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData": { "properties": { "libraries": { "anyOf": [ @@ -1589,7 +1650,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1635,121 +1696,60 @@ "title": "user" } }, - "title": "PlexSourceData", - "type": "object" - }, - "Record": { - "title": "Record", - "type": "object" - }, - "ScrobbleThresholds": { - "properties": { - "duration": { - "default": 240, - "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", - "examples": [ - 240 - ], - "title": "duration", - "type": "number" - }, - "percent": { - "default": 50, - "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.", - "examples": [ - 50 - ], - "title": "percent", - "type": "number" - } - }, - "title": "ScrobbleThresholds", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", "type": "object" }, - "SourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig": { "anyOf": [ { - "$ref": "#/definitions/SpotifySourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig" }, { - "$ref": "#/definitions/PlexSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig" }, { - "$ref": "#/definitions/TautulliSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig" }, { - "$ref": "#/definitions/DeezerSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig" }, { - "$ref": "#/definitions/SubsonicSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig" }, { - "$ref": "#/definitions/JellySourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig" }, { - "$ref": "#/definitions/LastFmSouceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig" }, { - "$ref": "#/definitions/YTMusicSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig" }, { - "$ref": "#/definitions/MPRISSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig" }, { - "$ref": "#/definitions/MopidySourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig" }, { - "$ref": "#/definitions/ListenBrainzSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig" }, { - "$ref": "#/definitions/JRiverSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig" }, { - "$ref": "#/definitions/KodiSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig" }, { - "$ref": "#/definitions/WebScrobblerSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig" }, { - "$ref": "#/definitions/ChromecastSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig" } ], - "title": "SourceAIOConfig" - }, - "SourceRetryOptions": { - "properties": { - "maxPollRetries": { - "default": 5, - "description": "default # of automatic polling restarts on error", - "examples": [ - 5 - ], - "title": "maxPollRetries", - "type": "number" - }, - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", - "examples": [ - 1 - ], - "title": "maxRequestRetries", - "type": "number" - }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", - "examples": [ - 1.5 - ], - "title": "retryMultiplier", - "type": "number" - } - }, - "title": "SourceRetryOptions", - "type": "object" + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig" }, - "SpotifySourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1766,7 +1766,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/SpotifySourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", "title": "data" }, "enable": { @@ -1784,7 +1784,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -1799,10 +1799,10 @@ "data", "type" ], - "title": "SpotifySourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig", "type": "object" }, - "SpotifySourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData": { "properties": { "clientId": { "description": "spotify client id", @@ -1879,7 +1879,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -1889,11 +1889,17 @@ "clientSecret", "redirectUri" ], - "title": "SpotifySourceData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", "type": "object" }, - "SubsonicData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData": { "properties": { + "ignoreTlsErrors": { + "default": false, + "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.", + "title": "ignoreTlsErrors", + "type": "boolean" + }, "interval": { "default": 10, "description": "How long to wait before polling the source API for new tracks (in seconds)", @@ -1903,6 +1909,12 @@ "title": "interval", "type": "number" }, + "legacyAuthentication": { + "default": false, + "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", + "title": "legacyAuthentication", + "type": "boolean" + }, "maxInterval": { "default": 30, "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)", @@ -1952,7 +1964,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1975,10 +1987,10 @@ "url", "user" ], - "title": "SubsonicData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", "type": "object" }, - "SubsonicSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1995,7 +2007,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/SubsonicData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", "title": "data" }, "enable": { @@ -2013,7 +2025,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -2028,10 +2040,10 @@ "data", "type" ], - "title": "SubsonicSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig", "type": "object" }, - "TautulliSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2048,7 +2060,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/PlexSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", "title": "data" }, "enable": { @@ -2066,7 +2078,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -2081,10 +2093,10 @@ "data", "type" ], - "title": "TautulliSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig", "type": "object" }, - "WebScrobblerData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData": { "properties": { "blacklist": { "anyOf": [ @@ -2163,7 +2175,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -2195,10 +2207,10 @@ "title": "whitelist" } }, - "title": "WebScrobblerData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", "type": "object" }, - "WebScrobblerSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2215,7 +2227,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/WebScrobblerData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", "title": "data" }, "enable": { @@ -2233,7 +2245,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -2247,10 +2259,10 @@ "required": [ "type" ], - "title": "WebScrobblerSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig", "type": "object" }, - "YTMusicData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData": { "properties": { "authUser": { "description": "If the 'X-Goog-AuthUser' header is present in the Request Headers for music.youtube.com it must also be included", @@ -2315,7 +2327,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -2323,10 +2335,10 @@ "required": [ "cookie" ], - "title": "YTMusicData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", "type": "object" }, - "YTMusicSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2343,7 +2355,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/YTMusicData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", "title": "data" }, "enable": { @@ -2361,7 +2373,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -2376,18 +2388,18 @@ "data", "type" ], - "title": "YTMusicSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig", "type": "object" } }, "properties": { "sourceDefaults": { - "$ref": "#/definitions/SourceRetryOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).SourceRetryOptions", "title": "sourceDefaults" }, "sources": { "items": { - "$ref": "#/definitions/SourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig" }, "title": "sources", "type": "array" diff --git a/src/backend/common/schema/aio.json b/src/backend/common/schema/aio.json index bd9ea628..4bf0612f 100644 --- a/src/backend/common/schema/aio.json +++ b/src/backend/common/schema/aio.json @@ -1,82 +1,64 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "ChromecastData": { + "LogLevel": { + "enum": [ + "debug", + "error", + "info", + "verbose", + "warn" + ], + "title": "LogLevel", + "type": "string" + }, + "Record": { + "title": "Record", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/Atomic\",{assert:{\"resolution-mode\":\"import\"}}).LogOptions": { "properties": { - "allowUnknownMedia": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "boolean" - } - ], - "default": false, - "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", - "title": "allowUnknownMedia" - }, - "blacklistApps": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } - ], - "description": "DO NOT scrobble from any application that START WITH these values, case-insensitive", - "examples": [ - [ - "spotify", - "pandora" - ] + "console": { + "description": "Specify the minimum log level streamed to the console (or docker container)", + "enum": [ + "debug", + "error", + false, + "info", + "verbose", + "warn" ], - "title": "blacklistApps" + "title": "console" }, - "blacklistDevices": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "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" - ] + "file": { + "description": "Specify the minimum log level to output to rotating files. If `false` no log files will be created.", + "enum": [ + "debug", + "error", + false, + "info", + "verbose", + "warn" ], - "title": "blacklistDevices" - }, - "devices": { - "description": "A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set", - "items": { - "$ref": "#/definitions/ChromecastDeviceInfo" - }, - "title": "devices", - "type": "array" + "title": "file" }, - "forceMediaRecognitionOn": { - "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", - "items": { - "type": "string" - }, - "title": "forceMediaRecognitionOn", - "type": "array" + "level": { + "$ref": "#/definitions/LogLevel", + "default": "'info'", + "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.", + "title": "level" }, + "stream": { + "$ref": "#/definitions/LogLevel", + "description": "Specify the minimum log level streamed to the UI", + "title": "stream" + } + }, + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/Atomic\",{assert:{\"resolution-mode\":\"import\"}}).LogOptions", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/aioConfig\",{assert:{\"resolution-mode\":\"import\"}}).SourceDefaults": { + "properties": { "maxPollRetries": { "default": 5, "description": "default # of automatic polling restarts on error", @@ -96,7 +78,7 @@ "type": "number" }, "options": { - "$ref": "#/definitions/Record", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "retryMultiplier": { @@ -109,161 +91,29 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" - }, - "useAutoDiscovery": { - "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", - "title": "useAutoDiscovery", - "type": "boolean" - }, - "useAvahi": { - "default": false, - "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.", - "title": "useAvahi", - "type": "boolean" - }, - "whitelistApps": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "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" - ] - ], - "title": "whitelistApps" - }, - "whitelistDevices": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "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" - ] - ], - "title": "whitelistDevices" - } - }, - "title": "ChromecastData", - "type": "object" - }, - "ChromecastDeviceInfo": { - "properties": { - "address": { - "description": "The IP address of the device", - "examples": [ - "192.168.0.115" - ], - "title": "address", - "type": "string" - }, - "name": { - "description": "A friendly name to identify this device", - "examples": [ - "MySmartTV" - ], - "title": "name", - "type": "string" - } - }, - "required": [ - "address", - "name" - ], - "title": "ChromecastDeviceInfo", - "type": "object" - }, - "ChromecastSourceAIOConfig": { - "properties": { - "clients": { - "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", - "examples": [ - [ - "MyMalojaConfigName", - "MyLastFMConfigName" - ] - ], - "items": { - "type": "string" - }, - "title": "clients", - "type": "array" - }, - "data": { - "$ref": "#/definitions/ChromecastData", - "title": "data" - }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", - "examples": [ - true - ], - "title": "enable", - "type": "boolean" - }, - "name": { - "description": "Unique identifier for this source.", - "title": "name", - "type": "string" - }, - "options": { - "$ref": "#/definitions/CommonSourceOptions", - "title": "options" - }, - "type": { - "enum": [ - "chromecast" - ], - "title": "type", - "type": "string" } }, - "required": [ - "data", - "type" - ], - "title": "ChromecastSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/aioConfig\",{assert:{\"resolution-mode\":\"import\"}}).SourceDefaults", "type": "object" }, - "ClientAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig": { "anyOf": [ { - "$ref": "#/definitions/LastfmClientAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig" }, { - "$ref": "#/definitions/ListenBrainzClientAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig" }, { - "$ref": "#/definitions/MalojaClientAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig" } ], - "title": "ClientAIOConfig" + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig" }, - "CommonClientData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData": { "properties": { "maxRequestRetries": { "default": 1, @@ -292,7 +142,7 @@ 1 ], "title": "deadLetterRetries", - "type": "boolean" + "type": "number" }, "refreshEnabled": { "default": true, @@ -307,7 +157,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/MatchLoggingOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", "title": "match" } }, @@ -328,91 +178,112 @@ "type": "number" } }, - "title": "CommonClientData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData", "type": "object" }, - "CommonSourceOptions": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions": { + "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", "properties": { - "logFilterFailure": { - "default": "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.", - "enum": [ - "debug", - false, - "warn" - ], - "examples": [ - "warn" - ], - "title": "logFilterFailure" - }, - "logPayload": { + "confidenceBreakdown": { "default": false, - "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", + "description": "Include confidence breakdowns in track match logging, if applicable", "examples": [ false ], - "title": "logPayload", + "title": "confidenceBreakdown", "type": "boolean" }, - "logPlayerState": { + "onMatch": { "default": false, - "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", + "description": "Log to DEBUG when a new track DOES match an existing scrobble", "examples": [ false ], - "title": "logPlayerState", + "title": "onMatch", "type": "boolean" }, - "scrobbleBacklog": { - "default": true, - "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", + "onNoMatch": { + "default": false, + "description": "Log to DEBUG when a new track does NOT match an existing scrobble", "examples": [ - true, false ], - "title": "scrobbleBacklog", + "title": "onNoMatch", "type": "boolean" } }, - "title": "CommonSourceOptions", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", "type": "object" }, - "DeezerData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig": { "properties": { - "clientId": { - "description": "deezer client id", + "configureAs": { + "default": "client", + "description": "Should always be `client` when using LastFM as a client", + "enum": [ + "client", + "source" + ], "examples": [ - "a89cba1569901a0671d5a9875fed4be1" + "client" ], - "title": "clientId", + "title": "configureAs", "type": "string" }, - "clientSecret": { - "description": "deezer client secret", + "data": { + "allOf": [ + { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData" + }, + { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData" + } + ], + "description": "Specific data required to configure this client", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", "examples": [ - "ec42e09d5ae0ee0f0816ca151008412a" + true ], - "title": "clientSecret", - "type": "string" + "title": "enable", + "type": "boolean" }, - "interval": { - "default": 60, - "description": "optional, how long to wait before calling spotify for new tracks (in seconds)", + "name": { + "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", "examples": [ - 60 + "MyConfig" ], - "title": "interval", - "type": "number" + "title": "name", + "type": "string" }, - "maxPollRetries": { - "default": 5, - "description": "default # of automatic polling restarts on error", + "type": { + "enum": [ + "lastfm" + ], + "title": "type", + "type": "string" + } + }, + "required": [ + "data", + "name", + "type" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData": { + "properties": { + "apiKey": { + "description": "API Key generated from Last.fm account", "examples": [ - 5 + "787c921a2a2ab42320831aba0c8f2fc2" ], - "title": "maxPollRetries", - "type": "number" + "title": "apiKey", + "type": "string" }, "maxRequestRetries": { "default": 1, @@ -423,15 +294,11 @@ "title": "maxRequestRetries", "type": "number" }, - "options": { - "$ref": "#/definitions/Record", - "title": "options" - }, "redirectUri": { - "default": "http://localhost:9078/deezer/callback", - "description": "deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"", + "default": "http://localhost:9078/lastfm/callback", + "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.", "examples": [ - "http://localhost:9078/deezer/callback" + "http://localhost:9078/lastfm/callback" ], "title": "redirectUri", "type": "string" @@ -445,38 +312,45 @@ "title": "retryMultiplier", "type": "number" }, - "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.", - "title": "scrobbleThresholds" + "secret": { + "description": "Secret generated from Last.fm account", + "examples": [ + "ec42e09d5ae0ee0f0816ca151008412a" + ], + "title": "secret", + "type": "string" + }, + "session": { + "description": "Optional session id returned from a completed auth flow", + "title": "session", + "type": "string" } }, "required": [ - "clientId", - "clientSecret", - "redirectUri" + "apiKey", + "secret" ], - "title": "DeezerData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData", "type": "object" }, - "DeezerSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig": { "properties": { - "clients": { - "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", + "configureAs": { + "default": "client", + "description": "Should always be `client` when using Listenbrainz as a client", + "enum": [ + "client", + "source" + ], "examples": [ - [ - "MyMalojaConfigName", - "MyLastFMConfigName" - ] + "client" ], - "items": { - "type": "string" - }, - "title": "clients", - "type": "array" + "title": "configureAs", + "type": "string" }, "data": { - "$ref": "#/definitions/DeezerData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", + "description": "Specific data required to configure this client", "title": "data" }, "enable": { @@ -489,17 +363,16 @@ "type": "boolean" }, "name": { - "description": "Unique identifier for this source.", + "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", + "examples": [ + "MyConfig" + ], "title": "name", "type": "string" }, - "options": { - "$ref": "#/definitions/CommonSourceOptions", - "title": "options" - }, "type": { "enum": [ - "deezer" + "listenbrainz" ], "title": "type", "type": "string" @@ -507,79 +380,481 @@ }, "required": [ "data", + "name", "type" ], - "title": "DeezerSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig", "type": "object" }, - "GotifyConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData": { "properties": { - "name": { - "description": "A friendly name used to identify webhook config in logs", - "title": "name", - "type": "string" + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" }, - "priorities": { - "$ref": "#/definitions/PrioritiesConfig", - "description": "Priority of messages\n\n* Info -> 5\n* Warn -> 7\n* Error -> 10", - "title": "priorities" + "options": { + "properties": { + "checkExistingScrobbles": { + "default": true, + "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.", + "examples": [ + true + ], + "title": "checkExistingScrobbles", + "type": "boolean" + }, + "deadLetterRetries": { + "default": 1, + "description": "Number of times MS should automatically retry scrobbles in dead letter queue", + "examples": [ + 1 + ], + "title": "deadLetterRetries", + "type": "number" + }, + "refreshEnabled": { + "default": true, + "description": "Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history", + "examples": [ + true + ], + "title": "refreshEnabled", + "type": "boolean" + }, + "verbose": { + "description": "Options used for increasing verbosity of logging in MS (used for debugging)", + "properties": { + "match": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", + "title": "match" + } + }, + "title": "verbose", + "type": "object" + } + }, + "title": "options", + "type": "object" }, - "token": { - "description": "The token created for this Application in Gotify", + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", "examples": [ - "AQZI58fA.rfSZbm" + 1.5 ], - "title": "token", - "type": "string" + "title": "retryMultiplier", + "type": "number" }, - "type": { - "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", - "enum": [ - "gotify", - "ntfy" - ], + "token": { + "description": "User token for the user to scrobble for", "examples": [ - "gotify" + "6794186bf-1157-4de6-80e5-uvb411f3ea2b" ], - "title": "type", + "title": "token", "type": "string" }, "url": { - "description": "The URL of the Gotify server. Same URL that would be used to reach the Gotify UI", + "default": "https://api.listenbrainz.org/", + "description": "URL for the ListenBrainz server, if not using the default", "examples": [ - "http://192.168.0.100:8078" + "https://api.listenbrainz.org/" ], "title": "url", "type": "string" + }, + "username": { + "description": "Username of the user to scrobble for", + "title": "username", + "type": "string" } }, "required": [ "token", - "type", - "url" + "username" ], - "title": "GotifyConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", "type": "object" }, - "JRiverData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig": { "properties": { - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 + "data": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", + "description": "Specific data required to configure this client", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", + "examples": [ + true + ], + "title": "enable", + "type": "boolean" + }, + "name": { + "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", + "examples": [ + "MyConfig" + ], + "title": "name", + "type": "string" + }, + "type": { + "enum": [ + "maloja" + ], + "title": "type", + "type": "string" + } + }, + "required": [ + "data", + "name", + "type" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData": { + "properties": { + "apiKey": { + "description": "API Key for Maloja server", + "examples": [ + "myApiKey" + ], + "title": "apiKey", + "type": "string" + }, + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" + }, + "options": { + "properties": { + "checkExistingScrobbles": { + "default": true, + "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.", + "examples": [ + true + ], + "title": "checkExistingScrobbles", + "type": "boolean" + }, + "deadLetterRetries": { + "default": 1, + "description": "Number of times MS should automatically retry scrobbles in dead letter queue", + "examples": [ + 1 + ], + "title": "deadLetterRetries", + "type": "number" + }, + "refreshEnabled": { + "default": true, + "description": "Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history", + "examples": [ + true + ], + "title": "refreshEnabled", + "type": "boolean" + }, + "verbose": { + "description": "Options used for increasing verbosity of logging in MS (used for debugging)", + "properties": { + "match": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", + "title": "match" + } + }, + "title": "verbose", + "type": "object" + } + }, + "title": "options", + "type": "object" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "examples": [ + 1.5 + ], + "title": "retryMultiplier", + "type": "number" + }, + "url": { + "description": "URL for maloja server", + "examples": [ + "http://localhost:42010" + ], + "title": "url", + "type": "string" + } + }, + "required": [ + "apiKey", + "url" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions": { + "properties": { + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "examples": [ + 1.5 + ], + "title": "retryMultiplier", + "type": "number" + } + }, + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).GotifyConfig": { + "properties": { + "name": { + "description": "A friendly name used to identify webhook config in logs", + "title": "name", + "type": "string" + }, + "priorities": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).PrioritiesConfig", + "description": "Priority of messages\n\n* Info -> 5\n* Warn -> 7\n* Error -> 10", + "title": "priorities" + }, + "token": { + "description": "The token created for this Application in Gotify", + "examples": [ + "AQZI58fA.rfSZbm" + ], + "title": "token", + "type": "string" + }, + "type": { + "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", + "enum": [ + "gotify", + "ntfy" + ], + "examples": [ + "gotify" + ], + "title": "type", + "type": "string" + }, + "url": { + "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" + ], + "title": "url", + "type": "string" + } + }, + "required": [ + "token", + "type", + "url" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).GotifyConfig", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).NtfyConfig": { + "properties": { + "name": { + "description": "A friendly name used to identify webhook config in logs", + "title": "name", + "type": "string" + }, + "password": { + "description": "Required if topic is protected", + "title": "password", + "type": "string" + }, + "priorities": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).PrioritiesConfig", + "description": "Priority of messages\n\n* Info -> 3\n* Warn -> 4\n* Error -> 5", + "title": "priorities" + }, + "topic": { + "description": "The topic mutli-scrobbler should POST to", + "title": "topic", + "type": "string" + }, + "type": { + "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", + "enum": [ + "gotify", + "ntfy" + ], + "examples": [ + "gotify" + ], + "title": "type", + "type": "string" + }, + "url": { + "description": "The URL of the Ntfy server", + "examples": [ + "http://192.168.0.100:8078" + ], + "title": "url", + "type": "string" + }, + "username": { + "description": "Required if topic is protected", + "title": "username", + "type": "string" + } + }, + "required": [ + "topic", + "type", + "url" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).NtfyConfig", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).PrioritiesConfig": { + "properties": { + "error": { + "examples": [ + 10 + ], + "title": "error", + "type": "number" + }, + "info": { + "examples": [ + 5 + ], + "title": "info", + "type": "number" + }, + "warn": { + "examples": [ + 7 + ], + "title": "warn", + "type": "number" + } + }, + "required": [ + "error", + "info", + "warn" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).PrioritiesConfig", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).WebhookConfig": { + "anyOf": [ + { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).GotifyConfig" + }, + { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).NtfyConfig" + } + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).WebhookConfig" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData": { + "properties": { + "allowUnknownMedia": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "boolean" + } + ], + "default": false, + "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", + "title": "allowUnknownMedia" + }, + "blacklistApps": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } ], - "title": "interval", - "type": "number" + "description": "DO NOT scrobble from any application that START WITH these values, case-insensitive", + "examples": [ + [ + "spotify", + "pandora" + ] + ], + "title": "blacklistApps" }, - "maxInterval": { - "default": 30, - "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)", + "blacklistDevices": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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": [ - 30 + [ + "home-mini", + "family-tv" + ] ], - "title": "maxInterval", - "type": "number" + "title": "blacklistDevices" + }, + "devices": { + "description": "A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set", + "items": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo" + }, + "title": "devices", + "type": "array" + }, + "forceMediaRecognitionOn": { + "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", + "items": { + "type": "string" + }, + "title": "forceMediaRecognitionOn", + "type": "array" }, "maxPollRetries": { "default": 5, @@ -603,11 +878,6 @@ "$ref": "#/definitions/Record", "title": "options" }, - "password": { - "description": "If you have enabled authentication, the password you set", - "title": "password", - "type": "string" - }, "retryMultiplier": { "default": 1.5, "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", @@ -618,32 +888,228 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "url": { - "default": "http://localhost:52199/MCWS/v1/", - "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/`", + "useAutoDiscovery": { + "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", + "title": "useAutoDiscovery", + "type": "boolean" + }, + "useAvahi": { + "default": false, + "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.", + "title": "useAvahi", + "type": "boolean" + }, + "whitelistApps": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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": [ - "http://localhost:52199/MCWS/v1/" + [ + "spotify", + "pandora" + ] ], - "title": "url", + "title": "whitelistApps" + }, + "whitelistDevices": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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" + ] + ], + "title": "whitelistDevices" + } + }, + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo": { + "properties": { + "address": { + "description": "The IP address of the device", + "examples": [ + "192.168.0.115" + ], + "title": "address", "type": "string" }, - "username": { - "description": "If you have enabled authentication, the username you set", - "title": "username", + "name": { + "description": "A friendly name to identify this device", + "examples": [ + "MySmartTV" + ], + "title": "name", + "type": "string" + } + }, + "required": [ + "address", + "name" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig": { + "properties": { + "clients": { + "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", + "examples": [ + [ + "MyMalojaConfigName", + "MyLastFMConfigName" + ] + ], + "items": { + "type": "string" + }, + "title": "clients", + "type": "array" + }, + "data": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", + "examples": [ + true + ], + "title": "enable", + "type": "boolean" + }, + "name": { + "description": "Unique identifier for this source.", + "title": "name", + "type": "string" + }, + "options": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "title": "options" + }, + "type": { + "enum": [ + "chromecast" + ], + "title": "type", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData": { + "properties": { + "clientId": { + "description": "deezer client id", + "examples": [ + "a89cba1569901a0671d5a9875fed4be1" + ], + "title": "clientId", + "type": "string" + }, + "clientSecret": { + "description": "deezer client secret", + "examples": [ + "ec42e09d5ae0ee0f0816ca151008412a" + ], + "title": "clientSecret", + "type": "string" + }, + "interval": { + "default": 60, + "description": "optional, how long to wait before calling spotify for new tracks (in seconds)", + "examples": [ + 60 + ], + "title": "interval", + "type": "number" + }, + "maxPollRetries": { + "default": 5, + "description": "default # of automatic polling restarts on error", + "examples": [ + 5 + ], + "title": "maxPollRetries", + "type": "number" + }, + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" + }, + "options": { + "$ref": "#/definitions/Record", + "title": "options" + }, + "redirectUri": { + "default": "http://localhost:9078/deezer/callback", + "description": "deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"", + "examples": [ + "http://localhost:9078/deezer/callback" + ], + "title": "redirectUri", "type": "string" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "examples": [ + 1.5 + ], + "title": "retryMultiplier", + "type": "number" + }, + "scrobbleThresholds": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", + "title": "scrobbleThresholds" } }, "required": [ - "url" + "clientId", + "clientSecret", + "redirectUri" ], - "title": "JRiverData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", "type": "object" }, - "JRiverSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -660,7 +1126,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/JRiverData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", "title": "data" }, "enable": { @@ -678,12 +1144,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "jriver" + "deezer" ], "title": "type", "type": "string" @@ -693,10 +1159,81 @@ "data", "type" ], - "title": "JRiverSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions": { + "properties": { + "logFilterFailure": { + "default": "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.", + "enum": [ + "debug", + false, + "warn" + ], + "examples": [ + "warn" + ], + "title": "logFilterFailure" + }, + "logPayload": { + "default": false, + "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", + "examples": [ + false + ], + "title": "logPayload", + "type": "boolean" + }, + "logPlayerState": { + "default": false, + "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", + "examples": [ + false + ], + "title": "logPlayerState", + "type": "boolean" + }, + "scrobbleBacklog": { + "default": true, + "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", + "examples": [ + true, + false + ], + "title": "scrobbleBacklog", + "type": "boolean" + } + }, + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds": { + "properties": { + "duration": { + "default": 240, + "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", + "examples": [ + 240 + ], + "title": "duration", + "type": "number" + }, + "percent": { + "default": 50, + "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.", + "examples": [ + 50 + ], + "title": "percent", + "type": "number" + } + }, + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds", "type": "object" }, - "JellyData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData": { "properties": { "maxPollRetries": { "default": 5, @@ -755,7 +1292,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -801,10 +1338,10 @@ "title": "users" } }, - "title": "JellyData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", "type": "object" }, - "JellySourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -821,7 +1358,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/JellyData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", "title": "data" }, "enable": { @@ -839,7 +1376,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -854,10 +1391,10 @@ "data", "type" ], - "title": "JellySourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig", "type": "object" }, - "KodiData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData": { "properties": { "interval": { "default": 10, @@ -900,7 +1437,7 @@ "title": "options" }, "password": { - "description": "The password set for Remote Control via Web Sever", + "description": "If you have enabled authentication, the password you set", "title": "password", "type": "string" }, @@ -914,87 +1451,32 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, "url": { - "default": "http://localhost:8080/jsonrpc", - "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`", + "default": "http://localhost:52199/MCWS/v1/", + "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:8080/jsonrpc" + "http://localhost:52199/MCWS/v1/" ], "title": "url", "type": "string" }, "username": { - "description": "The username set for Remote Control via Web Sever", + "description": "If you have enabled authentication, the username you set", "title": "username", "type": "string" } }, "required": [ - "password", - "url", - "username" - ], - "title": "KodiData", - "type": "object" - }, - "KodiSourceAIOConfig": { - "properties": { - "clients": { - "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", - "examples": [ - [ - "MyMalojaConfigName", - "MyLastFMConfigName" - ] - ], - "items": { - "type": "string" - }, - "title": "clients", - "type": "array" - }, - "data": { - "$ref": "#/definitions/KodiData", - "title": "data" - }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", - "examples": [ - true - ], - "title": "enable", - "type": "boolean" - }, - "name": { - "description": "Unique identifier for this source.", - "title": "name", - "type": "string" - }, - "options": { - "$ref": "#/definitions/CommonSourceOptions", - "title": "options" - }, - "type": { - "enum": [ - "kodi" - ], - "title": "type", - "type": "string" - } - }, - "required": [ - "data", - "type" + "url" ], - "title": "KodiSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", "type": "object" }, - "LastFmSouceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1010,20 +1492,8 @@ "title": "clients", "type": "array" }, - "configureAs": { - "default": "source", - "description": "When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.", - "enum": [ - "source" - ], - "examples": [ - "source" - ], - "title": "configureAs", - "type": "string" - }, "data": { - "$ref": "#/definitions/LastFmSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", "title": "data" }, "enable": { @@ -1041,12 +1511,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "lastfm" + "jriver" ], "title": "type", "type": "string" @@ -1056,19 +1526,11 @@ "data", "type" ], - "title": "LastFmSouceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig", "type": "object" }, - "LastFmSourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData": { "properties": { - "apiKey": { - "description": "API Key generated from Last.fm account", - "examples": [ - "787c921a2a2ab42320831aba0c8f2fc2" - ], - "title": "apiKey", - "type": "string" - }, "interval": { "default": 10, "description": "How long to wait before polling the source API for new tracks (in seconds)", @@ -1109,13 +1571,9 @@ "$ref": "#/definitions/Record", "title": "options" }, - "redirectUri": { - "default": "http://localhost:9078/lastfm/callback", - "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.", - "examples": [ - "http://localhost:9078/lastfm/callback" - ], - "title": "redirectUri", + "password": { + "description": "The password set for Remote Control via Web Sever", + "title": "password", "type": "string" }, "retryMultiplier": { @@ -1128,56 +1586,116 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "secret": { - "description": "Secret generated from Last.fm account", + "url": { + "default": "http://localhost:8080/jsonrpc", + "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": [ - "ec42e09d5ae0ee0f0816ca151008412a" + "http://localhost:8080/jsonrpc" ], - "title": "secret", + "title": "url", "type": "string" }, - "session": { - "description": "Optional session id returned from a completed auth flow", - "title": "session", + "username": { + "description": "The username set for Remote Control via Web Sever", + "title": "username", "type": "string" } }, "required": [ - "apiKey", - "secret" + "password", + "url", + "username" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig": { + "properties": { + "clients": { + "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", + "examples": [ + [ + "MyMalojaConfigName", + "MyLastFMConfigName" + ] + ], + "items": { + "type": "string" + }, + "title": "clients", + "type": "array" + }, + "data": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", + "examples": [ + true + ], + "title": "enable", + "type": "boolean" + }, + "name": { + "description": "Unique identifier for this source.", + "title": "name", + "type": "string" + }, + "options": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "title": "options" + }, + "type": { + "enum": [ + "kodi" + ], + "title": "type", + "type": "string" + } + }, + "required": [ + "data", + "type" ], - "title": "LastFmSourceData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig", "type": "object" }, - "LastfmClientAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig": { "properties": { + "clients": { + "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", + "examples": [ + [ + "MyMalojaConfigName", + "MyLastFMConfigName" + ] + ], + "items": { + "type": "string" + }, + "title": "clients", + "type": "array" + }, "configureAs": { - "default": "client", - "description": "Should always be `client` when using LastFM as a client", + "default": "source", + "description": "When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.", "enum": [ - "client", "source" ], "examples": [ - "client" + "source" ], "title": "configureAs", "type": "string" }, "data": { - "allOf": [ - { - "$ref": "#/definitions/CommonClientData" - }, - { - "$ref": "#/definitions/LastfmData" - } - ], - "description": "Specific data required to configure this client", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", "title": "data" }, "enable": { @@ -1190,13 +1708,14 @@ "type": "boolean" }, "name": { - "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", - "examples": [ - "MyConfig" - ], + "description": "Unique identifier for this source.", "title": "name", "type": "string" }, + "options": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "title": "options" + }, "type": { "enum": [ "lastfm" @@ -1207,13 +1726,12 @@ }, "required": [ "data", - "name", "type" ], - "title": "LastfmClientAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig", "type": "object" }, - "LastfmData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData": { "properties": { "apiKey": { "description": "API Key generated from Last.fm account", @@ -1223,6 +1741,33 @@ "title": "apiKey", "type": "string" }, + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", + "examples": [ + 10 + ], + "title": "interval", + "type": "number" + }, + "maxInterval": { + "default": 30, + "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)", + "examples": [ + 30 + ], + "title": "maxInterval", + "type": "number" + }, + "maxPollRetries": { + "default": 5, + "description": "default # of automatic polling restarts on error", + "examples": [ + 5 + ], + "title": "maxPollRetries", + "type": "number" + }, "maxRequestRetries": { "default": 1, "description": "default # of http request retries a source can make before error is thrown", @@ -1232,6 +1777,10 @@ "title": "maxRequestRetries", "type": "number" }, + "options": { + "$ref": "#/definitions/Record", + "title": "options" + }, "redirectUri": { "default": "http://localhost:9078/lastfm/callback", "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.", @@ -1250,6 +1799,11 @@ "title": "retryMultiplier", "type": "number" }, + "scrobbleThresholds": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", + "title": "scrobbleThresholds" + }, "secret": { "description": "Secret generated from Last.fm account", "examples": [ @@ -1268,157 +1822,10 @@ "apiKey", "secret" ], - "title": "LastfmData", - "type": "object" - }, - "ListenBrainzClientAIOConfig": { - "properties": { - "configureAs": { - "default": "client", - "description": "Should always be `client` when using Listenbrainz as a client", - "enum": [ - "client", - "source" - ], - "examples": [ - "client" - ], - "title": "configureAs", - "type": "string" - }, - "data": { - "$ref": "#/definitions/ListenBrainzClientData", - "description": "Specific data required to configure this client", - "title": "data" - }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", - "examples": [ - true - ], - "title": "enable", - "type": "boolean" - }, - "name": { - "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", - "examples": [ - "MyConfig" - ], - "title": "name", - "type": "string" - }, - "type": { - "enum": [ - "listenbrainz" - ], - "title": "type", - "type": "string" - } - }, - "required": [ - "data", - "name", - "type" - ], - "title": "ListenBrainzClientAIOConfig", - "type": "object" - }, - "ListenBrainzClientData": { - "properties": { - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", - "examples": [ - 1 - ], - "title": "maxRequestRetries", - "type": "number" - }, - "options": { - "properties": { - "checkExistingScrobbles": { - "default": true, - "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.", - "examples": [ - true - ], - "title": "checkExistingScrobbles", - "type": "boolean" - }, - "deadLetterRetries": { - "default": 1, - "description": "Number of times MS should automatically retry scrobbles in dead letter queue", - "examples": [ - 1 - ], - "title": "deadLetterRetries", - "type": "boolean" - }, - "refreshEnabled": { - "default": true, - "description": "Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history", - "examples": [ - true - ], - "title": "refreshEnabled", - "type": "boolean" - }, - "verbose": { - "description": "Options used for increasing verbosity of logging in MS (used for debugging)", - "properties": { - "match": { - "$ref": "#/definitions/MatchLoggingOptions", - "title": "match" - } - }, - "title": "verbose", - "type": "object" - } - }, - "title": "options", - "type": "object" - }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", - "examples": [ - 1.5 - ], - "title": "retryMultiplier", - "type": "number" - }, - "token": { - "description": "User token for the user to scrobble for", - "examples": [ - "6794186bf-1157-4de6-80e5-uvb411f3ea2b" - ], - "title": "token", - "type": "string" - }, - "url": { - "default": "https://api.listenbrainz.org/", - "description": "URL for the ListenBrainz server, if not using the default", - "examples": [ - "https://api.listenbrainz.org/" - ], - "title": "url", - "type": "string" - }, - "username": { - "description": "Username of the user to scrobble for", - "title": "username", - "type": "string" - } - }, - "required": [ - "token", - "username" - ], - "title": "ListenBrainzClientData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", "type": "object" }, - "ListenBrainzSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1447,7 +1854,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/ListenBrainzSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", "title": "data" }, "enable": { @@ -1465,7 +1872,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -1480,10 +1887,10 @@ "data", "type" ], - "title": "ListenBrainzSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig", "type": "object" }, - "ListenBrainzSourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData": { "properties": { "interval": { "default": 10, @@ -1535,7 +1942,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1558,91 +1965,53 @@ }, "username": { "description": "Username of the user to scrobble for", - "title": "username", - "type": "string" - } - }, - "required": [ - "token", - "username" - ], - "title": "ListenBrainzSourceData", - "type": "object" - }, - "LogLevel": { - "enum": [ - "debug", - "error", - "info", - "verbose", - "warn" - ], - "title": "LogLevel", - "type": "string" - }, - "LogOptions": { - "properties": { - "console": { - "description": "Specify the minimum log level streamed to the console (or docker container)", - "enum": [ - "debug", - "error", - false, - "info", - "verbose", - "warn" - ], - "title": "console" - }, - "file": { - "description": "Specify the minimum log level to output to rotating files. If `false` no log files will be created.", - "enum": [ - "debug", - "error", - false, - "info", - "verbose", - "warn" - ], - "title": "file" - }, - "level": { - "$ref": "#/definitions/LogLevel", - "default": "'info'", - "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.", - "title": "level" - }, - "stream": { - "$ref": "#/definitions/LogLevel", - "description": "Specify the minimum log level streamed to the UI", - "title": "stream" + "title": "username", + "type": "string" } }, - "title": "LogOptions", + "required": [ + "token", + "username" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", "type": "object" }, - "MPRISData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData": { "properties": { - "blacklist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } + "albumBlacklist": { + "default": [ + "Soundcloud" ], - "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", + "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": [ [ - "spotify", - "vlc" + "Soundcloud", + "Mixcloud" ] ], - "title": "blacklist" + "items": { + "type": "string" + }, + "title": "albumBlacklist", + "type": "array" + }, + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", + "examples": [ + 10 + ], + "title": "interval", + "type": "number" + }, + "maxInterval": { + "default": 30, + "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)", + "examples": [ + 30 + ], + "title": "maxInterval", + "type": "number" }, "maxPollRetries": { "default": 5, @@ -1676,36 +2045,40 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "whitelist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } - ], - "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", + "uriBlacklist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriBlacklist", + "type": "array" + }, + "uriWhitelist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriWhitelist", + "type": "array" + }, + "url": { + "default": "ws://localhost:6680/mopidy/ws/", + "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": [ - [ - "spotify", - "vlc" - ] + "ws://localhost:6680/mopidy/ws/" ], - "title": "whitelist" + "title": "url", + "type": "string" } }, - "title": "MPRISData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", "type": "object" }, - "MPRISSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1722,7 +2095,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/MPRISData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", "title": "data" }, "enable": { @@ -1740,51 +2113,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "mpris" - ], - "title": "type", - "type": "string" - } - }, - "required": [ - "data", - "type" - ], - "title": "MPRISSourceAIOConfig", - "type": "object" - }, - "MalojaClientAIOConfig": { - "properties": { - "data": { - "$ref": "#/definitions/MalojaClientData", - "description": "Specific data required to configure this client", - "title": "data" - }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", - "examples": [ - true - ], - "title": "enable", - "type": "boolean" - }, - "name": { - "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", - "examples": [ - "MyConfig" - ], - "title": "name", - "type": "string" - }, - "type": { - "enum": [ - "maloja" + "mopidy" ], "title": "type", "type": "string" @@ -1792,170 +2126,33 @@ }, "required": [ "data", - "name", "type" ], - "title": "MalojaClientAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig", "type": "object" }, - "MalojaClientData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData": { "properties": { - "apiKey": { - "description": "API Key for Maloja server", - "examples": [ - "myApiKey" - ], - "title": "apiKey", - "type": "string" - }, - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", - "examples": [ - 1 - ], - "title": "maxRequestRetries", - "type": "number" - }, - "options": { - "properties": { - "checkExistingScrobbles": { - "default": true, - "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.", - "examples": [ - true - ], - "title": "checkExistingScrobbles", - "type": "boolean" - }, - "deadLetterRetries": { - "default": 1, - "description": "Number of times MS should automatically retry scrobbles in dead letter queue", - "examples": [ - 1 - ], - "title": "deadLetterRetries", - "type": "boolean" - }, - "refreshEnabled": { - "default": true, - "description": "Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history", - "examples": [ - true - ], - "title": "refreshEnabled", - "type": "boolean" - }, - "verbose": { - "description": "Options used for increasing verbosity of logging in MS (used for debugging)", - "properties": { - "match": { - "$ref": "#/definitions/MatchLoggingOptions", - "title": "match" - } + "blacklist": { + "anyOf": [ + { + "items": { + "type": "string" }, - "title": "verbose", - "type": "object" - } - }, - "title": "options", - "type": "object" - }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", - "examples": [ - 1.5 - ], - "title": "retryMultiplier", - "type": "number" - }, - "url": { - "description": "URL for maloja server", - "examples": [ - "http://localhost:42010" - ], - "title": "url", - "type": "string" - } - }, - "required": [ - "apiKey", - "url" - ], - "title": "MalojaClientData", - "type": "object" - }, - "MatchLoggingOptions": { - "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", - "properties": { - "confidenceBreakdown": { - "default": false, - "description": "Include confidence breakdowns in track match logging, if applicable", - "examples": [ - false - ], - "title": "confidenceBreakdown", - "type": "boolean" - }, - "onMatch": { - "default": false, - "description": "Log to DEBUG when a new track DOES match an existing scrobble", - "examples": [ - false - ], - "title": "onMatch", - "type": "boolean" - }, - "onNoMatch": { - "default": false, - "description": "Log to DEBUG when a new track does NOT match an existing scrobble", - "examples": [ - false - ], - "title": "onNoMatch", - "type": "boolean" - } - }, - "title": "MatchLoggingOptions", - "type": "object" - }, - "MopidyData": { - "properties": { - "albumBlacklist": { - "default": [ - "Soundcloud" + "type": "array" + }, + { + "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.", + "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", "examples": [ [ - "Soundcloud", - "Mixcloud" + "spotify", + "vlc" ] ], - "items": { - "type": "string" - }, - "title": "albumBlacklist", - "type": "array" - }, - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 - ], - "title": "interval", - "type": "number" - }, - "maxInterval": { - "default": 30, - "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)", - "examples": [ - 30 - ], - "title": "maxInterval", - "type": "number" + "title": "blacklist" }, "maxPollRetries": { "default": 5, @@ -1989,40 +2186,36 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "uriBlacklist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriBlacklist", - "type": "array" - }, - "uriWhitelist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriWhitelist", - "type": "array" - }, - "url": { - "default": "ws://localhost:6680/mopidy/ws/", - "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/`", + "whitelist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", "examples": [ - "ws://localhost:6680/mopidy/ws/" + [ + "spotify", + "vlc" + ] ], - "title": "url", - "type": "string" + "title": "whitelist" } }, - "title": "MopidyData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", "type": "object" }, - "MopidySourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2039,7 +2232,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/MopidyData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", "title": "data" }, "enable": { @@ -2057,12 +2250,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "mopidy" + "mpris" ], "title": "type", "type": "string" @@ -2072,66 +2265,10 @@ "data", "type" ], - "title": "MopidySourceAIOConfig", - "type": "object" - }, - "NtfyConfig": { - "properties": { - "name": { - "description": "A friendly name used to identify webhook config in logs", - "title": "name", - "type": "string" - }, - "password": { - "description": "Required if topic is protected", - "title": "password", - "type": "string" - }, - "priorities": { - "$ref": "#/definitions/PrioritiesConfig", - "description": "Priority of messages\n\n* Info -> 3\n* Warn -> 4\n* Error -> 5", - "title": "priorities" - }, - "topic": { - "description": "The topic mutli-scrobbler should POST to", - "title": "topic", - "type": "string" - }, - "type": { - "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", - "enum": [ - "gotify", - "ntfy" - ], - "examples": [ - "gotify" - ], - "title": "type", - "type": "string" - }, - "url": { - "description": "The URL of the Ntfy server", - "examples": [ - "http://192.168.0.100:8078" - ], - "title": "url", - "type": "string" - }, - "username": { - "description": "Required if topic is protected", - "title": "username", - "type": "string" - } - }, - "required": [ - "topic", - "type", - "url" - ], - "title": "NtfyConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig", "type": "object" }, - "PlexSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2148,7 +2285,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/PlexSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", "title": "data" }, "enable": { @@ -2166,7 +2303,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -2181,10 +2318,10 @@ "data", "type" ], - "title": "PlexSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig", "type": "object" }, - "PlexSourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData": { "properties": { "libraries": { "anyOf": [ @@ -2255,7 +2392,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -2301,186 +2438,60 @@ "title": "user" } }, - "title": "PlexSourceData", - "type": "object" - }, - "PrioritiesConfig": { - "properties": { - "error": { - "examples": [ - 10 - ], - "title": "error", - "type": "number" - }, - "info": { - "examples": [ - 5 - ], - "title": "info", - "type": "number" - }, - "warn": { - "examples": [ - 7 - ], - "title": "warn", - "type": "number" - } - }, - "required": [ - "error", - "info", - "warn" - ], - "title": "PrioritiesConfig", - "type": "object" - }, - "Record": { - "title": "Record", - "type": "object" - }, - "RequestRetryOptions": { - "properties": { - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", - "examples": [ - 1 - ], - "title": "maxRequestRetries", - "type": "number" - }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", - "examples": [ - 1.5 - ], - "title": "retryMultiplier", - "type": "number" - } - }, - "title": "RequestRetryOptions", - "type": "object" - }, - "ScrobbleThresholds": { - "properties": { - "duration": { - "default": 240, - "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", - "examples": [ - 240 - ], - "title": "duration", - "type": "number" - }, - "percent": { - "default": 50, - "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.", - "examples": [ - 50 - ], - "title": "percent", - "type": "number" - } - }, - "title": "ScrobbleThresholds", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", "type": "object" }, - "SourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig": { "anyOf": [ { - "$ref": "#/definitions/SpotifySourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig" }, { - "$ref": "#/definitions/PlexSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig" }, { - "$ref": "#/definitions/TautulliSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig" }, { - "$ref": "#/definitions/DeezerSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig" }, { - "$ref": "#/definitions/SubsonicSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig" }, { - "$ref": "#/definitions/JellySourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig" }, { - "$ref": "#/definitions/LastFmSouceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig" }, { - "$ref": "#/definitions/YTMusicSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig" }, { - "$ref": "#/definitions/MPRISSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig" }, { - "$ref": "#/definitions/MopidySourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig" }, { - "$ref": "#/definitions/ListenBrainzSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig" }, { - "$ref": "#/definitions/JRiverSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig" }, { - "$ref": "#/definitions/KodiSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig" }, { - "$ref": "#/definitions/WebScrobblerSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig" }, { - "$ref": "#/definitions/ChromecastSourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig" } ], - "title": "SourceAIOConfig" - }, - "SourceDefaults": { - "properties": { - "maxPollRetries": { - "default": 5, - "description": "default # of automatic polling restarts on error", - "examples": [ - 5 - ], - "title": "maxPollRetries", - "type": "number" - }, - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", - "examples": [ - 1 - ], - "title": "maxRequestRetries", - "type": "number" - }, - "options": { - "$ref": "#/definitions/CommonSourceOptions", - "title": "options" - }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", - "examples": [ - 1.5 - ], - "title": "retryMultiplier", - "type": "number" - }, - "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.", - "title": "scrobbleThresholds" - } - }, - "title": "SourceDefaults", - "type": "object" + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig" }, - "SpotifySourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2497,7 +2508,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/SpotifySourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", "title": "data" }, "enable": { @@ -2515,7 +2526,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -2530,10 +2541,10 @@ "data", "type" ], - "title": "SpotifySourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig", "type": "object" }, - "SpotifySourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData": { "properties": { "clientId": { "description": "spotify client id", @@ -2610,7 +2621,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -2620,11 +2631,17 @@ "clientSecret", "redirectUri" ], - "title": "SpotifySourceData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", "type": "object" }, - "SubsonicData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData": { "properties": { + "ignoreTlsErrors": { + "default": false, + "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.", + "title": "ignoreTlsErrors", + "type": "boolean" + }, "interval": { "default": 10, "description": "How long to wait before polling the source API for new tracks (in seconds)", @@ -2634,6 +2651,12 @@ "title": "interval", "type": "number" }, + "legacyAuthentication": { + "default": false, + "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", + "title": "legacyAuthentication", + "type": "boolean" + }, "maxInterval": { "default": 30, "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)", @@ -2683,7 +2706,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -2706,10 +2729,10 @@ "url", "user" ], - "title": "SubsonicData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", "type": "object" }, - "SubsonicSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2726,7 +2749,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/SubsonicData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", "title": "data" }, "enable": { @@ -2744,7 +2767,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -2759,10 +2782,10 @@ "data", "type" ], - "title": "SubsonicSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig", "type": "object" }, - "TautulliSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2779,7 +2802,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/PlexSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", "title": "data" }, "enable": { @@ -2797,7 +2820,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -2812,10 +2835,10 @@ "data", "type" ], - "title": "TautulliSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig", "type": "object" }, - "WebScrobblerData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData": { "properties": { "blacklist": { "anyOf": [ @@ -2894,7 +2917,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -2926,10 +2949,10 @@ "title": "whitelist" } }, - "title": "WebScrobblerData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", "type": "object" }, - "WebScrobblerSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2946,7 +2969,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/WebScrobblerData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", "title": "data" }, "enable": { @@ -2964,7 +2987,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -2978,21 +3001,10 @@ "required": [ "type" ], - "title": "WebScrobblerSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig", "type": "object" }, - "WebhookConfig": { - "anyOf": [ - { - "$ref": "#/definitions/GotifyConfig" - }, - { - "$ref": "#/definitions/NtfyConfig" - } - ], - "title": "WebhookConfig" - }, - "YTMusicData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData": { "properties": { "authUser": { "description": "If the 'X-Goog-AuthUser' header is present in the Request Headers for music.youtube.com it must also be included", @@ -3057,7 +3069,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -3065,10 +3077,10 @@ "required": [ "cookie" ], - "title": "YTMusicData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", "type": "object" }, - "YTMusicSourceAIOConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -3085,7 +3097,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/YTMusicData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", "title": "data" }, "enable": { @@ -3103,7 +3115,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" }, "type": { @@ -3118,7 +3130,7 @@ "data", "type" ], - "title": "YTMusicSourceAIOConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig", "type": "object" } }, @@ -3135,12 +3147,12 @@ "type": "string" }, "clientDefaults": { - "$ref": "#/definitions/RequestRetryOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions", "title": "clientDefaults" }, "clients": { "items": { - "$ref": "#/definitions/ClientAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig" }, "title": "clients", "type": "array" @@ -3155,7 +3167,7 @@ "type": "boolean" }, "logging": { - "$ref": "#/definitions/LogOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/Atomic\",{assert:{\"resolution-mode\":\"import\"}}).LogOptions", "title": "logging" }, "port": { @@ -3168,19 +3180,19 @@ "type": "number" }, "sourceDefaults": { - "$ref": "#/definitions/SourceDefaults", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/aioConfig\",{assert:{\"resolution-mode\":\"import\"}}).SourceDefaults", "title": "sourceDefaults" }, "sources": { "items": { - "$ref": "#/definitions/SourceAIOConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig" }, "title": "sources", "type": "array" }, "webhooks": { "items": { - "$ref": "#/definitions/WebhookConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).WebhookConfig" }, "title": "webhooks", "type": "array" diff --git a/src/backend/common/schema/client.json b/src/backend/common/schema/client.json index 1178af0a..299f7c39 100644 --- a/src/backend/common/schema/client.json +++ b/src/backend/common/schema/client.json @@ -2,17 +2,17 @@ "$schema": "http://json-schema.org/draft-07/schema#", "anyOf": [ { - "$ref": "#/definitions/LastfmClientConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientConfig" }, { - "$ref": "#/definitions/ListenBrainzClientConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientConfig" }, { - "$ref": "#/definitions/MalojaClientConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientConfig" } ], "definitions": { - "CommonClientData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData": { "properties": { "maxRequestRetries": { "default": 1, @@ -41,7 +41,7 @@ 1 ], "title": "deadLetterRetries", - "type": "boolean" + "type": "number" }, "refreshEnabled": { "default": true, @@ -56,7 +56,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/MatchLoggingOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", "title": "match" } }, @@ -77,10 +77,44 @@ "type": "number" } }, - "title": "CommonClientData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions": { + "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", + "properties": { + "confidenceBreakdown": { + "default": false, + "description": "Include confidence breakdowns in track match logging, if applicable", + "examples": [ + false + ], + "title": "confidenceBreakdown", + "type": "boolean" + }, + "onMatch": { + "default": false, + "description": "Log to DEBUG when a new track DOES match an existing scrobble", + "examples": [ + false + ], + "title": "onMatch", + "type": "boolean" + }, + "onNoMatch": { + "default": false, + "description": "Log to DEBUG when a new track does NOT match an existing scrobble", + "examples": [ + false + ], + "title": "onNoMatch", + "type": "boolean" + } + }, + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", "type": "object" }, - "LastfmClientConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientConfig": { "properties": { "configureAs": { "default": "client", @@ -98,10 +132,10 @@ "data": { "allOf": [ { - "$ref": "#/definitions/CommonClientData" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData" }, { - "$ref": "#/definitions/LastfmData" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData" } ], "description": "Specific data required to configure this client", @@ -129,10 +163,10 @@ "data", "name" ], - "title": "LastfmClientConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientConfig", "type": "object" }, - "LastfmData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData": { "properties": { "apiKey": { "description": "API Key generated from Last.fm account", @@ -187,10 +221,10 @@ "apiKey", "secret" ], - "title": "LastfmData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData", "type": "object" }, - "ListenBrainzClientConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientConfig": { "properties": { "configureAs": { "default": "client", @@ -206,7 +240,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/ListenBrainzClientData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", "description": "Specific data required to configure this client", "title": "data" }, @@ -232,10 +266,10 @@ "data", "name" ], - "title": "ListenBrainzClientConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientConfig", "type": "object" }, - "ListenBrainzClientData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData": { "properties": { "maxRequestRetries": { "default": 1, @@ -264,7 +298,7 @@ 1 ], "title": "deadLetterRetries", - "type": "boolean" + "type": "number" }, "refreshEnabled": { "default": true, @@ -279,7 +313,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/MatchLoggingOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", "title": "match" } }, @@ -326,13 +360,13 @@ "token", "username" ], - "title": "ListenBrainzClientData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", "type": "object" }, - "MalojaClientConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientConfig": { "properties": { "data": { - "$ref": "#/definitions/MalojaClientData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", "description": "Specific data required to configure this client", "title": "data" }, @@ -358,10 +392,10 @@ "data", "name" ], - "title": "MalojaClientConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientConfig", "type": "object" }, - "MalojaClientData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData": { "properties": { "apiKey": { "description": "API Key for Maloja server", @@ -398,7 +432,7 @@ 1 ], "title": "deadLetterRetries", - "type": "boolean" + "type": "number" }, "refreshEnabled": { "default": true, @@ -413,7 +447,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/MatchLoggingOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", "title": "match" } }, @@ -446,41 +480,7 @@ "apiKey", "url" ], - "title": "MalojaClientData", - "type": "object" - }, - "MatchLoggingOptions": { - "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", - "properties": { - "confidenceBreakdown": { - "default": false, - "description": "Include confidence breakdowns in track match logging, if applicable", - "examples": [ - false - ], - "title": "confidenceBreakdown", - "type": "boolean" - }, - "onMatch": { - "default": false, - "description": "Log to DEBUG when a new track DOES match an existing scrobble", - "examples": [ - false - ], - "title": "onMatch", - "type": "boolean" - }, - "onNoMatch": { - "default": false, - "description": "Log to DEBUG when a new track does NOT match an existing scrobble", - "examples": [ - false - ], - "title": "onNoMatch", - "type": "boolean" - } - }, - "title": "MatchLoggingOptions", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", "type": "object" } } diff --git a/src/backend/common/schema/source.json b/src/backend/common/schema/source.json index 8f85ea57..c602b5ae 100644 --- a/src/backend/common/schema/source.json +++ b/src/backend/common/schema/source.json @@ -2,53 +2,57 @@ "$schema": "http://json-schema.org/draft-07/schema#", "anyOf": [ { - "$ref": "#/definitions/SpotifySourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceConfig" }, { - "$ref": "#/definitions/PlexSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceConfig" }, { - "$ref": "#/definitions/TautulliSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceConfig" }, { - "$ref": "#/definitions/DeezerSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceConfig" }, { - "$ref": "#/definitions/SubSonicSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubSonicSourceConfig" }, { - "$ref": "#/definitions/JellySourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceConfig" }, { - "$ref": "#/definitions/LastfmSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmSourceConfig" }, { - "$ref": "#/definitions/YTMusicSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceConfig" }, { - "$ref": "#/definitions/MPRISSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceConfig" }, { - "$ref": "#/definitions/MopidySourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceConfig" }, { - "$ref": "#/definitions/ListenBrainzSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceConfig" }, { - "$ref": "#/definitions/JRiverSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceConfig" }, { - "$ref": "#/definitions/KodiSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceConfig" }, { - "$ref": "#/definitions/WebScrobblerSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceConfig" }, { - "$ref": "#/definitions/ChromecastSourceConfig" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceConfig" } ], "definitions": { - "ChromecastData": { + "Record": { + "title": "Record", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData": { "properties": { "allowUnknownMedia": { "anyOf": [ @@ -111,7 +115,7 @@ "devices": { "description": "A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set", "items": { - "$ref": "#/definitions/ChromecastDeviceInfo" + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo" }, "title": "devices", "type": "array" @@ -156,7 +160,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -214,10 +218,10 @@ "title": "whitelistDevices" } }, - "title": "ChromecastData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", "type": "object" }, - "ChromecastDeviceInfo": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo": { "properties": { "address": { "description": "The IP address of the device", @@ -240,10 +244,10 @@ "address", "name" ], - "title": "ChromecastDeviceInfo", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo", "type": "object" }, - "ChromecastSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -260,7 +264,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/ChromecastData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", "title": "data" }, "enable": { @@ -278,64 +282,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "ChromecastSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceConfig", "type": "object" }, - "CommonSourceOptions": { - "properties": { - "logFilterFailure": { - "default": "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.", - "enum": [ - "debug", - false, - "warn" - ], - "examples": [ - "warn" - ], - "title": "logFilterFailure" - }, - "logPayload": { - "default": false, - "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", - "examples": [ - false - ], - "title": "logPayload", - "type": "boolean" - }, - "logPlayerState": { - "default": false, - "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", - "examples": [ - false - ], - "title": "logPlayerState", - "type": "boolean" - }, - "scrobbleBacklog": { - "default": true, - "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", - "examples": [ - true, - false - ], - "title": "scrobbleBacklog", - "type": "boolean" - } - }, - "title": "CommonSourceOptions", - "type": "object" - }, - "DeezerData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData": { "properties": { "clientId": { "description": "deezer client id", @@ -403,7 +360,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -413,10 +370,10 @@ "clientSecret", "redirectUri" ], - "title": "DeezerData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", "type": "object" }, - "DeezerSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -433,7 +390,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/DeezerData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", "title": "data" }, "enable": { @@ -451,144 +408,88 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "DeezerSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceConfig", "type": "object" }, - "JRiverData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions": { "properties": { - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 - ], - "title": "interval", - "type": "number" - }, - "maxInterval": { - "default": 30, - "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)", - "examples": [ - 30 + "logFilterFailure": { + "default": "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.", + "enum": [ + "debug", + false, + "warn" ], - "title": "maxInterval", - "type": "number" - }, - "maxPollRetries": { - "default": 5, - "description": "default # of automatic polling restarts on error", "examples": [ - 5 + "warn" ], - "title": "maxPollRetries", - "type": "number" + "title": "logFilterFailure" }, - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", + "logPayload": { + "default": false, + "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", "examples": [ - 1 + false ], - "title": "maxRequestRetries", - "type": "number" - }, - "options": { - "$ref": "#/definitions/Record", - "title": "options" - }, - "password": { - "description": "If you have enabled authentication, the password you set", - "title": "password", - "type": "string" + "title": "logPayload", + "type": "boolean" }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "logPlayerState": { + "default": false, + "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", "examples": [ - 1.5 + false ], - "title": "retryMultiplier", - "type": "number" - }, - "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.", - "title": "scrobbleThresholds" + "title": "logPlayerState", + "type": "boolean" }, - "url": { - "default": "http://localhost:52199/MCWS/v1/", - "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/`", + "scrobbleBacklog": { + "default": true, + "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", "examples": [ - "http://localhost:52199/MCWS/v1/" + true, + false ], - "title": "url", - "type": "string" - }, - "username": { - "description": "If you have enabled authentication, the username you set", - "title": "username", - "type": "string" + "title": "scrobbleBacklog", + "type": "boolean" } }, - "required": [ - "url" - ], - "title": "JRiverData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "type": "object" }, - "JRiverSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds": { "properties": { - "clients": { - "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", + "duration": { + "default": 240, + "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", "examples": [ - [ - "MyMalojaConfigName", - "MyLastFMConfigName" - ] + 240 ], - "items": { - "type": "string" - }, - "title": "clients", - "type": "array" - }, - "data": { - "$ref": "#/definitions/JRiverData", - "title": "data" + "title": "duration", + "type": "number" }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", + "percent": { + "default": 50, + "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.", "examples": [ - true + 50 ], - "title": "enable", - "type": "boolean" - }, - "name": { - "description": "Unique identifier for this source.", - "title": "name", - "type": "string" - }, - "options": { - "$ref": "#/definitions/CommonSourceOptions", - "title": "options" + "title": "percent", + "type": "number" } }, - "required": [ - "data" - ], - "title": "JRiverSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds", "type": "object" }, - "JellyData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData": { "properties": { "maxPollRetries": { "default": 5, @@ -647,7 +548,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -693,10 +594,137 @@ "title": "users" } }, - "title": "JellyData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceConfig": { + "properties": { + "clients": { + "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", + "examples": [ + [ + "MyMalojaConfigName", + "MyLastFMConfigName" + ] + ], + "items": { + "type": "string" + }, + "title": "clients", + "type": "array" + }, + "data": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", + "examples": [ + true + ], + "title": "enable", + "type": "boolean" + }, + "name": { + "description": "Unique identifier for this source.", + "title": "name", + "type": "string" + }, + "options": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "title": "options" + } + }, + "required": [ + "data" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceConfig", + "type": "object" + }, + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData": { + "properties": { + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", + "examples": [ + 10 + ], + "title": "interval", + "type": "number" + }, + "maxInterval": { + "default": 30, + "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)", + "examples": [ + 30 + ], + "title": "maxInterval", + "type": "number" + }, + "maxPollRetries": { + "default": 5, + "description": "default # of automatic polling restarts on error", + "examples": [ + 5 + ], + "title": "maxPollRetries", + "type": "number" + }, + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" + }, + "options": { + "$ref": "#/definitions/Record", + "title": "options" + }, + "password": { + "description": "If you have enabled authentication, the password you set", + "title": "password", + "type": "string" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "examples": [ + 1.5 + ], + "title": "retryMultiplier", + "type": "number" + }, + "scrobbleThresholds": { + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", + "title": "scrobbleThresholds" + }, + "url": { + "default": "http://localhost:52199/MCWS/v1/", + "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/" + ], + "title": "url", + "type": "string" + }, + "username": { + "description": "If you have enabled authentication, the username you set", + "title": "username", + "type": "string" + } + }, + "required": [ + "url" + ], + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", "type": "object" }, - "JellySourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -713,7 +741,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/JellyData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", "title": "data" }, "enable": { @@ -731,17 +759,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "JellySourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceConfig", "type": "object" }, - "KodiData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData": { "properties": { "interval": { "default": 10, @@ -798,7 +826,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -822,10 +850,10 @@ "url", "username" ], - "title": "KodiData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", "type": "object" }, - "KodiSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -842,7 +870,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/KodiData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", "title": "data" }, "enable": { @@ -860,17 +888,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "KodiSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceConfig", "type": "object" }, - "LastFmSourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData": { "properties": { "apiKey": { "description": "API Key generated from Last.fm account", @@ -939,7 +967,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -961,10 +989,10 @@ "apiKey", "secret" ], - "title": "LastFmSourceData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", "type": "object" }, - "LastfmSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -993,7 +1021,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/LastFmSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", "title": "data" }, "enable": { @@ -1011,17 +1039,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "LastfmSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmSourceConfig", "type": "object" }, - "ListenBrainzSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1050,7 +1078,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/ListenBrainzSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", "title": "data" }, "enable": { @@ -1068,17 +1096,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "ListenBrainzSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceConfig", "type": "object" }, - "ListenBrainzSourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData": { "properties": { "interval": { "default": 10, @@ -1130,7 +1158,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1161,31 +1189,45 @@ "token", "username" ], - "title": "ListenBrainzSourceData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", "type": "object" }, - "MPRISData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData": { "properties": { - "blacklist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } + "albumBlacklist": { + "default": [ + "Soundcloud" ], - "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", + "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": [ [ - "spotify", - "vlc" + "Soundcloud", + "Mixcloud" ] ], - "title": "blacklist" + "items": { + "type": "string" + }, + "title": "albumBlacklist", + "type": "array" + }, + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", + "examples": [ + 10 + ], + "title": "interval", + "type": "number" + }, + "maxInterval": { + "default": 30, + "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)", + "examples": [ + 30 + ], + "title": "maxInterval", + "type": "number" }, "maxPollRetries": { "default": 5, @@ -1219,36 +1261,40 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "whitelist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } - ], - "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", + "uriBlacklist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriBlacklist", + "type": "array" + }, + "uriWhitelist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriWhitelist", + "type": "array" + }, + "url": { + "default": "ws://localhost:6680/mopidy/ws/", + "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": [ - [ - "spotify", - "vlc" - ] + "ws://localhost:6680/mopidy/ws/" ], - "title": "whitelist" + "title": "url", + "type": "string" } }, - "title": "MPRISData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", "type": "object" }, - "MPRISSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1265,7 +1311,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/MPRISData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", "title": "data" }, "enable": { @@ -1283,52 +1329,38 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "MPRISSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceConfig", "type": "object" }, - "MopidyData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData": { "properties": { - "albumBlacklist": { - "default": [ - "Soundcloud" + "blacklist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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.", + "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", "examples": [ [ - "Soundcloud", - "Mixcloud" + "spotify", + "vlc" ] ], - "items": { - "type": "string" - }, - "title": "albumBlacklist", - "type": "array" - }, - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 - ], - "title": "interval", - "type": "number" - }, - "maxInterval": { - "default": 30, - "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)", - "examples": [ - 30 - ], - "title": "maxInterval", - "type": "number" + "title": "blacklist" }, "maxPollRetries": { "default": 5, @@ -1362,40 +1394,36 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "uriBlacklist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriBlacklist", - "type": "array" - }, - "uriWhitelist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriWhitelist", - "type": "array" - }, - "url": { - "default": "ws://localhost:6680/mopidy/ws/", - "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/`", + "whitelist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", "examples": [ - "ws://localhost:6680/mopidy/ws/" + [ + "spotify", + "vlc" + ] ], - "title": "url", - "type": "string" + "title": "whitelist" } }, - "title": "MopidyData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", "type": "object" }, - "MopidySourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1412,7 +1440,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/MopidyData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", "title": "data" }, "enable": { @@ -1430,17 +1458,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "MopidySourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceConfig", "type": "object" }, - "PlexSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1457,7 +1485,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/PlexSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", "title": "data" }, "enable": { @@ -1475,17 +1503,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "PlexSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceConfig", "type": "object" }, - "PlexSourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData": { "properties": { "libraries": { "anyOf": [ @@ -1556,7 +1584,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1602,38 +1630,10 @@ "title": "user" } }, - "title": "PlexSourceData", - "type": "object" - }, - "Record": { - "title": "Record", - "type": "object" - }, - "ScrobbleThresholds": { - "properties": { - "duration": { - "default": 240, - "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", - "examples": [ - 240 - ], - "title": "duration", - "type": "number" - }, - "percent": { - "default": 50, - "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.", - "examples": [ - 50 - ], - "title": "percent", - "type": "number" - } - }, - "title": "ScrobbleThresholds", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", "type": "object" }, - "SpotifySourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1650,7 +1650,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/SpotifySourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", "title": "data" }, "enable": { @@ -1668,17 +1668,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "SpotifySourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceConfig", "type": "object" }, - "SpotifySourceData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData": { "properties": { "clientId": { "description": "spotify client id", @@ -1755,7 +1755,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -1765,10 +1765,10 @@ "clientSecret", "redirectUri" ], - "title": "SpotifySourceData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", "type": "object" }, - "SubSonicSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubSonicSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1785,7 +1785,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/SubsonicData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", "title": "data" }, "enable": { @@ -1803,18 +1803,24 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "SubSonicSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubSonicSourceConfig", "type": "object" }, - "SubsonicData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData": { "properties": { + "ignoreTlsErrors": { + "default": false, + "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.", + "title": "ignoreTlsErrors", + "type": "boolean" + }, "interval": { "default": 10, "description": "How long to wait before polling the source API for new tracks (in seconds)", @@ -1824,6 +1830,12 @@ "title": "interval", "type": "number" }, + "legacyAuthentication": { + "default": false, + "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", + "title": "legacyAuthentication", + "type": "boolean" + }, "maxInterval": { "default": 30, "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)", @@ -1873,7 +1885,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1896,10 +1908,10 @@ "url", "user" ], - "title": "SubsonicData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", "type": "object" }, - "TautulliSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1916,7 +1928,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/PlexSourceData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", "title": "data" }, "enable": { @@ -1934,17 +1946,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "TautulliSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceConfig", "type": "object" }, - "WebScrobblerData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData": { "properties": { "blacklist": { "anyOf": [ @@ -2023,7 +2035,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -2055,10 +2067,10 @@ "title": "whitelist" } }, - "title": "WebScrobblerData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", "type": "object" }, - "WebScrobblerSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2075,7 +2087,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/WebScrobblerData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", "title": "data" }, "enable": { @@ -2093,14 +2105,14 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, - "title": "WebScrobblerSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceConfig", "type": "object" }, - "YTMusicData": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData": { "properties": { "authUser": { "description": "If the 'X-Goog-AuthUser' header is present in the Request Headers for music.youtube.com it must also be included", @@ -2165,7 +2177,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/ScrobbleThresholds", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -2173,10 +2185,10 @@ "required": [ "cookie" ], - "title": "YTMusicData", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", "type": "object" }, - "YTMusicSourceConfig": { + "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2193,7 +2205,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/YTMusicData", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", "title": "data" }, "enable": { @@ -2211,14 +2223,14 @@ "type": "string" }, "options": { - "$ref": "#/definitions/CommonSourceOptions", + "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "YTMusicSourceConfig", + "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceConfig", "type": "object" } } diff --git a/src/backend/sources/SubsonicSource.ts b/src/backend/sources/SubsonicSource.ts index e77972dd..2e90b8d6 100644 --- a/src/backend/sources/SubsonicSource.ts +++ b/src/backend/sources/SubsonicSource.ts @@ -13,6 +13,8 @@ import {isNodeNetworkException} from "../common/errors/NodeErrors.js"; import {ErrorWithCause} from "pony-cause"; import {UpstreamError} from "../common/errors/UpstreamError.js"; import {getSubsonicResponse, SubsonicResponse, SubsonicResponseCommon} from "../common/vendor/subsonic/interfaces.js"; +import {hash} from "@astronautlabs/mdns/dist/hash.js"; +import e from "express"; dayjs.extend(isSameOrAfter); @@ -76,17 +78,28 @@ export class SubsonicSource extends MemorySource { retryMultiplier = DEFAULT_RETRY_MULTIPLIER } = this.config.data; - - const salt = await crypto.randomBytes(10).toString('hex'); - const hash = crypto.createHash('md5').update(`${password}${salt}`).digest('hex') - req.query({ + const queryOpts: Record = { u: user, - t: hash, - s: salt, v: '1.15.0', c: `multi-scrobbler - ${this.name}`, f: 'json' - }); + }; + if((this.config?.data?.legacyAuthentication ?? false)) { + //queryOpts.p = password; + queryOpts.p = `enc:${Buffer.from(password).toString('hex')}` + } else { + const salt = await crypto.randomBytes(10).toString('hex'); + const hash = crypto.createHash('md5').update(`${password}${salt}`).digest('hex') + queryOpts.t = hash; + queryOpts.s = salt; + } + + req.query(queryOpts); + + if((this.config?.data?.ignoreTlsErrors ?? false)) { + req.disableTLSCerts(); + } + try { const resp = await req as SubsonicResponse; @@ -121,7 +134,22 @@ export class SubsonicSource extends MemorySource { } = body; if (status === 'failed') { - throw new UpstreamError(`Subsonic API returned an error => ${parseApiResponseErrorToThrowable(resp)}`, {response: resp}); + const uError = new UpstreamError(`Subsonic API returned an error => ${parseApiResponseErrorToThrowable(resp)}`, {response: resp}); + if(uError.message.includes('Subsonic Api Response => (41)')) { + const tokenError = 'This server does not support token-based authentication and must use the legacy authentication approach with sends your password in CLEAR TEXT.'; + if(this.config.data.legacyAuthentication !== undefined) { + if(this.config.data.legacyAuthentication === true) { + this.logger.error(`${tokenError} MS has already tried to use legacy authentication but it has failed. There is likely a different reason the server is rejecting authentication.`); + } else { + this.logger.error(`${tokenError} Your config settings do not allow legacy authentication to be used.`); + } + throw uError; + } else { + this.logger.warn(`${parseApiResponseErrorToThrowable(resp)} | ${tokenError} MS will attempt to use legacy authentication since 'legacyAuthentication' is not explicitly defined (or disabled) in config.`); + this.config.data.legacyAuthentication = true; + return await this.callApi(req); + } + } } // @ts-ignore @@ -142,6 +170,10 @@ export class SubsonicSource extends MemorySource { throw new UpstreamError('Could not communicate with Subsonic Server', {cause: e, showStopper: true}); } + if(e.message.includes('self-signed certificate')) { + throw new UpstreamError(`Subsonic server uses self-signed certs which MS does not allow by default. This error can be ignored by setting 'ignoreTlsErrors: true' in config. WARNING this can result in cleartext communication which is insecure.`, {cause: e, showStopper: true}); + } + throw new UpstreamError('Subsonic server response was unexpected', {cause: e}); } } -- 2.51.2 From 57f2e1d500dfc11bf57a2e2826dc68de01053c51 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 14 Feb 2024 09:44:47 -0500 Subject: [PATCH 06/34] feat: Log more URL when route is unknown --- src/backend/server/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index c9aea643..b9b7d838 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -454,7 +454,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput const remote = req.connection.remoteAddress; const proxyRemote = req.headers["x-forwarded-for"]; const ua = req.headers["user-agent"]; - logger.debug(`Server received ${req.method} request from ${remote}${proxyRemote !== undefined ? ` (${proxyRemote})` : ''}${ua !== undefined ? ` (UA: ${ua})` : ''} to unknown route: ${req.url}`); + logger.debug(`Server received ${req.method} request from ${remote}${proxyRemote !== undefined ? ` (${proxyRemote})` : ''}${ua !== undefined ? ` (UA: ${ua})` : ''} to unknown route: ${req.originalUrl}`); return res.sendStatus(404); }); } -- 2.51.2 From aef7716634532659901f0c192f76806af1a2263a Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 14 Feb 2024 09:45:14 -0500 Subject: [PATCH 07/34] fix(webscrobbler): Fix missing wildcard to match slug route #137 Closes #137 --- src/backend/server/webscrobblerRoutes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/server/webscrobblerRoutes.ts b/src/backend/server/webscrobblerRoutes.ts index 42966b56..815e86e0 100644 --- a/src/backend/server/webscrobblerRoutes.ts +++ b/src/backend/server/webscrobblerRoutes.ts @@ -22,7 +22,7 @@ export const setupWebscrobblerRoutes = (app: ExpressWithAsync, parentLogger: Log // } }); const webhookIngress = new WebhookNotifier(); - app.postAsync('/api/webscrobbler', + app.postAsync('/api/webscrobbler*', async function (req, res, next) { // track request before parsing body to ensure we at least log that something is happening // (in the event body parsing does not work or request is not POST/PATCH) -- 2.51.2 From 49b9ee96f3202b404ef9650b24851ad216f84071 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 14 Feb 2024 09:47:00 -0500 Subject: [PATCH 08/34] fix: Fix ts schema generation import issue Changing tsconfig resolution caused generation to generate new (bad?) import lines. Replacing with regex from https://github.com/YousefED/typescript-json-schema/issues/582 returned ref/definitions to normal --- patches/typescript-json-schema+0.55.0.patch | 26 + src/backend/common/schema/aio-client.json | 128 +- src/backend/common/schema/aio-source.json | 830 +++---- src/backend/common/schema/aio.json | 2458 +++++++++---------- src/backend/common/schema/client.json | 116 +- src/backend/common/schema/source.json | 744 +++--- 6 files changed, 2164 insertions(+), 2138 deletions(-) create mode 100644 patches/typescript-json-schema+0.55.0.patch diff --git a/patches/typescript-json-schema+0.55.0.patch b/patches/typescript-json-schema+0.55.0.patch new file mode 100644 index 00000000..a4839f56 --- /dev/null +++ b/patches/typescript-json-schema+0.55.0.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/typescript-json-schema/dist/typescript-json-schema.js b/node_modules/typescript-json-schema/dist/typescript-json-schema.js +index 23cc6d1..221adc2 100644 +--- a/node_modules/typescript-json-schema/dist/typescript-json-schema.js ++++ b/node_modules/typescript-json-schema/dist/typescript-json-schema.js +@@ -55,7 +55,7 @@ var crypto_1 = require("crypto"); + var ts = require("typescript"); + var path_equal_1 = require("path-equal"); + var vm = require("vm"); +-var REGEX_FILE_NAME_OR_SPACE = /(\bimport\(".*?"\)|".*?")\.| /g; ++var REGEX_FILE_NAME_OR_SPACE = /(\bimport\(".*?"(, \{ assert: \{ "resolution-mode": "(import|require)" \} \})?\)|".*?")\.| /g;// /(\bimport\(".*?"\)|".*?")\.| /g; + var REGEX_TSCONFIG_NAME = /^.*\.json$/; + var REGEX_TJS_JSDOC = /^-([\w]+)\s+(\S|\S[\s\S]*\S)\s*$/g; + var REGEX_GROUP_JSDOC = /^[.]?([\w]+)\s+(\S|\S[\s\S]*\S)\s*$/g; +diff --git a/node_modules/typescript-json-schema/typescript-json-schema.ts b/node_modules/typescript-json-schema/typescript-json-schema.ts +index 5908567..c188383 100644 +--- a/node_modules/typescript-json-schema/typescript-json-schema.ts ++++ b/node_modules/typescript-json-schema/typescript-json-schema.ts +@@ -9,7 +9,7 @@ export { Program, CompilerOptions, Symbol } from "typescript"; + + const vm = require("vm"); + +-const REGEX_FILE_NAME_OR_SPACE = /(\bimport\(".*?"\)|".*?")\.| /g; ++const REGEX_FILE_NAME_OR_SPACE = /(\bimport\(".*?"(, \{ assert: \{ "resolution-mode": "(import|require)" \} \})?\)|".*?")\.| /g;// /(\bimport\(".*?"\)|".*?")\.| /g; + const REGEX_TSCONFIG_NAME = /^.*\.json$/; + const REGEX_TJS_JSDOC = /^-([\w]+)\s+(\S|\S[\s\S]*\S)\s*$/g; + const REGEX_GROUP_JSDOC = /^[.]?([\w]+)\s+(\S|\S[\s\S]*\S)\s*$/g; diff --git a/src/backend/common/schema/aio-client.json b/src/backend/common/schema/aio-client.json index a4ecb037..179a49a4 100644 --- a/src/backend/common/schema/aio-client.json +++ b/src/backend/common/schema/aio-client.json @@ -1,21 +1,21 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig": { + "ClientAIOConfig": { "anyOf": [ { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig" + "$ref": "#/definitions/LastfmClientAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig" + "$ref": "#/definitions/ListenBrainzClientAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig" + "$ref": "#/definitions/MalojaClientAIOConfig" } ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig" + "title": "ClientAIOConfig" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData": { + "CommonClientData": { "properties": { "maxRequestRetries": { "default": 1, @@ -59,7 +59,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", + "$ref": "#/definitions/MatchLoggingOptions", "title": "match" } }, @@ -80,44 +80,10 @@ "type": "number" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData", + "title": "CommonClientData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions": { - "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", - "properties": { - "confidenceBreakdown": { - "default": false, - "description": "Include confidence breakdowns in track match logging, if applicable", - "examples": [ - false - ], - "title": "confidenceBreakdown", - "type": "boolean" - }, - "onMatch": { - "default": false, - "description": "Log to DEBUG when a new track DOES match an existing scrobble", - "examples": [ - false - ], - "title": "onMatch", - "type": "boolean" - }, - "onNoMatch": { - "default": false, - "description": "Log to DEBUG when a new track does NOT match an existing scrobble", - "examples": [ - false - ], - "title": "onNoMatch", - "type": "boolean" - } - }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig": { + "LastfmClientAIOConfig": { "properties": { "configureAs": { "default": "client", @@ -135,10 +101,10 @@ "data": { "allOf": [ { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData" + "$ref": "#/definitions/CommonClientData" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData" + "$ref": "#/definitions/LastfmData" } ], "description": "Specific data required to configure this client", @@ -174,10 +140,10 @@ "name", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig", + "title": "LastfmClientAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData": { + "LastfmData": { "properties": { "apiKey": { "description": "API Key generated from Last.fm account", @@ -232,10 +198,10 @@ "apiKey", "secret" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData", + "title": "LastfmData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig": { + "ListenBrainzClientAIOConfig": { "properties": { "configureAs": { "default": "client", @@ -251,7 +217,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", + "$ref": "#/definitions/ListenBrainzClientData", "description": "Specific data required to configure this client", "title": "data" }, @@ -285,10 +251,10 @@ "name", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig", + "title": "ListenBrainzClientAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData": { + "ListenBrainzClientData": { "properties": { "maxRequestRetries": { "default": 1, @@ -332,7 +298,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", + "$ref": "#/definitions/MatchLoggingOptions", "title": "match" } }, @@ -379,13 +345,13 @@ "token", "username" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", + "title": "ListenBrainzClientData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig": { + "MalojaClientAIOConfig": { "properties": { "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", + "$ref": "#/definitions/MalojaClientData", "description": "Specific data required to configure this client", "title": "data" }, @@ -419,10 +385,10 @@ "name", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig", + "title": "MalojaClientAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData": { + "MalojaClientData": { "properties": { "apiKey": { "description": "API Key for Maloja server", @@ -474,7 +440,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", + "$ref": "#/definitions/MatchLoggingOptions", "title": "match" } }, @@ -507,10 +473,44 @@ "apiKey", "url" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", + "title": "MalojaClientData", + "type": "object" + }, + "MatchLoggingOptions": { + "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", + "properties": { + "confidenceBreakdown": { + "default": false, + "description": "Include confidence breakdowns in track match logging, if applicable", + "examples": [ + false + ], + "title": "confidenceBreakdown", + "type": "boolean" + }, + "onMatch": { + "default": false, + "description": "Log to DEBUG when a new track DOES match an existing scrobble", + "examples": [ + false + ], + "title": "onMatch", + "type": "boolean" + }, + "onNoMatch": { + "default": false, + "description": "Log to DEBUG when a new track does NOT match an existing scrobble", + "examples": [ + false + ], + "title": "onNoMatch", + "type": "boolean" + } + }, + "title": "MatchLoggingOptions", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions": { + "RequestRetryOptions": { "properties": { "maxRequestRetries": { "default": 1, @@ -531,18 +531,18 @@ "type": "number" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions", + "title": "RequestRetryOptions", "type": "object" } }, "properties": { "clientDefaults": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions", + "$ref": "#/definitions/RequestRetryOptions", "title": "clientDefaults" }, "clients": { "items": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig" + "$ref": "#/definitions/ClientAIOConfig" }, "title": "clients", "type": "array" diff --git a/src/backend/common/schema/aio-source.json b/src/backend/common/schema/aio-source.json index 33723b89..4e9595fa 100644 --- a/src/backend/common/schema/aio-source.json +++ b/src/backend/common/schema/aio-source.json @@ -1,11 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Record": { - "title": "Record", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData": { + "ChromecastData": { "properties": { "allowUnknownMedia": { "anyOf": [ @@ -68,7 +64,7 @@ "devices": { "description": "A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set", "items": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo" + "$ref": "#/definitions/ChromecastDeviceInfo" }, "title": "devices", "type": "array" @@ -113,7 +109,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -171,10 +167,10 @@ "title": "whitelistDevices" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", + "title": "ChromecastData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo": { + "ChromecastDeviceInfo": { "properties": { "address": { "description": "The IP address of the device", @@ -197,10 +193,10 @@ "address", "name" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo", + "title": "ChromecastDeviceInfo", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig": { + "ChromecastSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -217,7 +213,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", + "$ref": "#/definitions/ChromecastData", "title": "data" }, "enable": { @@ -235,7 +231,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -250,10 +246,57 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig", + "title": "ChromecastSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData": { + "CommonSourceOptions": { + "properties": { + "logFilterFailure": { + "default": "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.", + "enum": [ + "debug", + false, + "warn" + ], + "examples": [ + "warn" + ], + "title": "logFilterFailure" + }, + "logPayload": { + "default": false, + "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", + "examples": [ + false + ], + "title": "logPayload", + "type": "boolean" + }, + "logPlayerState": { + "default": false, + "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", + "examples": [ + false + ], + "title": "logPlayerState", + "type": "boolean" + }, + "scrobbleBacklog": { + "default": true, + "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", + "examples": [ + true, + false + ], + "title": "scrobbleBacklog", + "type": "boolean" + } + }, + "title": "CommonSourceOptions", + "type": "object" + }, + "DeezerData": { "properties": { "clientId": { "description": "deezer client id", @@ -321,7 +364,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -331,10 +374,10 @@ "clientSecret", "redirectUri" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", + "title": "DeezerData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig": { + "DeezerSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -351,7 +394,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", + "$ref": "#/definitions/DeezerData", "title": "data" }, "enable": { @@ -369,7 +412,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -384,115 +427,29 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig", + "title": "DeezerSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions": { + "JRiverData": { "properties": { - "logFilterFailure": { - "default": "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.", - "enum": [ - "debug", - false, - "warn" - ], - "examples": [ - "warn" - ], - "title": "logFilterFailure" - }, - "logPayload": { - "default": false, - "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", - "examples": [ - false - ], - "title": "logPayload", - "type": "boolean" - }, - "logPlayerState": { - "default": false, - "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", - "examples": [ - false - ], - "title": "logPlayerState", - "type": "boolean" - }, - "scrobbleBacklog": { - "default": true, - "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", - "examples": [ - true, - false - ], - "title": "scrobbleBacklog", - "type": "boolean" - } - }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds": { - "properties": { - "duration": { - "default": 240, - "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", - "examples": [ - 240 - ], - "title": "duration", - "type": "number" - }, - "percent": { - "default": 50, - "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.", - "examples": [ - 50 - ], - "title": "percent", - "type": "number" - } - }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).SourceRetryOptions": { - "properties": { - "maxPollRetries": { - "default": 5, - "description": "default # of automatic polling restarts on error", + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", "examples": [ - 5 + 10 ], - "title": "maxPollRetries", + "title": "interval", "type": "number" }, - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", + "maxInterval": { + "default": 30, + "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)", "examples": [ - 1 + 30 ], - "title": "maxRequestRetries", + "title": "maxInterval", "type": "number" }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", - "examples": [ - 1.5 - ], - "title": "retryMultiplier", - "type": "number" - } - }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).SourceRetryOptions", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData": { - "properties": { "maxPollRetries": { "default": 5, "description": "default # of automatic polling restarts on error", @@ -512,33 +469,13 @@ "type": "number" }, "options": { - "description": "Additional options for jellyfin logging and tuning", - "properties": { - "logFilterFailure": { - "default": "warn", - "description": "How MS should log when a Jellyfin event fails a defined filter (users/servers)\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 Jellyfin sources. Set to `debug` or `false` to reduce log noise.", - "enum": [ - "debug", - false, - "warn" - ], - "examples": [ - "warn" - ], - "title": "logFilterFailure" - }, - "logPayload": { - "default": false, - "description": "Log raw Jellyfin webhook payload to debug", - "examples": [ - false - ], - "title": "logPayload", - "type": "boolean" - } - }, - "title": "options", - "type": "object" + "$ref": "#/definitions/Record", + "title": "options" + }, + "password": { + "description": "If you have enabled authentication, the password you set", + "title": "password", + "type": "string" }, "retryMultiplier": { "default": 1.5, @@ -550,56 +487,32 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "servers": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } - ], - "description": "optional list of servers to scrobble tracks from\n\nIf none are provided tracks from all servers will be scrobbled", + "url": { + "default": "http://localhost:52199/MCWS/v1/", + "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": [ - [ - "MyServerName1" - ] + "http://localhost:52199/MCWS/v1/" ], - "title": "servers" + "title": "url", + "type": "string" }, - "users": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "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" - ] - ], - "title": "users" + "username": { + "description": "If you have enabled authentication, the username you set", + "title": "username", + "type": "string" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", + "required": [ + "url" + ], + "title": "JRiverData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig": { + "JRiverSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -616,7 +529,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", + "$ref": "#/definitions/JRiverData", "title": "data" }, "enable": { @@ -634,12 +547,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "jellyfin" + "jriver" ], "title": "type", "type": "string" @@ -649,29 +562,11 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig", + "title": "JRiverSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData": { + "JellyData": { "properties": { - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 - ], - "title": "interval", - "type": "number" - }, - "maxInterval": { - "default": 30, - "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)", - "examples": [ - 30 - ], - "title": "maxInterval", - "type": "number" - }, "maxPollRetries": { "default": 5, "description": "default # of automatic polling restarts on error", @@ -691,13 +586,33 @@ "type": "number" }, "options": { - "$ref": "#/definitions/Record", - "title": "options" - }, - "password": { - "description": "If you have enabled authentication, the password you set", - "title": "password", - "type": "string" + "description": "Additional options for jellyfin logging and tuning", + "properties": { + "logFilterFailure": { + "default": "warn", + "description": "How MS should log when a Jellyfin event fails a defined filter (users/servers)\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 Jellyfin sources. Set to `debug` or `false` to reduce log noise.", + "enum": [ + "debug", + false, + "warn" + ], + "examples": [ + "warn" + ], + "title": "logFilterFailure" + }, + "logPayload": { + "default": false, + "description": "Log raw Jellyfin webhook payload to debug", + "examples": [ + false + ], + "title": "logPayload", + "type": "boolean" + } + }, + "title": "options", + "type": "object" }, "retryMultiplier": { "default": 1.5, @@ -709,32 +624,56 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "url": { - "default": "http://localhost:52199/MCWS/v1/", - "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/`", + "servers": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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": "servers" + }, + "users": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "optional list of users to scrobble tracks from\n\nIf none are provided tracks from all users will be scrobbled", "examples": [ - "http://localhost:52199/MCWS/v1/" + [ + "MyUser1", + "MyUser2" + ] ], - "title": "url", - "type": "string" - }, - "username": { - "description": "If you have enabled authentication, the username you set", - "title": "username", - "type": "string" + "title": "users" } }, - "required": [ - "url" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", + "title": "JellyData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig": { + "JellySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -751,7 +690,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", + "$ref": "#/definitions/JellyData", "title": "data" }, "enable": { @@ -769,12 +708,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "jriver" + "jellyfin" ], "title": "type", "type": "string" @@ -784,10 +723,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig", + "title": "JellySourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData": { + "KodiData": { "properties": { "interval": { "default": 10, @@ -844,7 +783,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -868,10 +807,10 @@ "url", "username" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", + "title": "KodiData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig": { + "KodiSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -888,7 +827,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", + "$ref": "#/definitions/KodiData", "title": "data" }, "enable": { @@ -906,7 +845,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -921,10 +860,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig", + "title": "KodiSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig": { + "LastFmSouceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -953,7 +892,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", + "$ref": "#/definitions/LastFmSourceData", "title": "data" }, "enable": { @@ -971,7 +910,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -986,10 +925,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig", + "title": "LastFmSouceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData": { + "LastFmSourceData": { "properties": { "apiKey": { "description": "API Key generated from Last.fm account", @@ -1058,7 +997,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1080,10 +1019,10 @@ "apiKey", "secret" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", + "title": "LastFmSourceData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig": { + "ListenBrainzSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1112,7 +1051,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", + "$ref": "#/definitions/ListenBrainzSourceData", "title": "data" }, "enable": { @@ -1130,7 +1069,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -1145,10 +1084,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig", + "title": "ListenBrainzSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData": { + "ListenBrainzSourceData": { "properties": { "interval": { "default": 10, @@ -1200,7 +1139,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1231,45 +1170,31 @@ "token", "username" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", + "title": "ListenBrainzSourceData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData": { + "MPRISData": { "properties": { - "albumBlacklist": { - "default": [ - "Soundcloud" + "blacklist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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.", + "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", "examples": [ [ - "Soundcloud", - "Mixcloud" + "spotify", + "vlc" ] ], - "items": { - "type": "string" - }, - "title": "albumBlacklist", - "type": "array" - }, - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 - ], - "title": "interval", - "type": "number" - }, - "maxInterval": { - "default": 30, - "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)", - "examples": [ - 30 - ], - "title": "maxInterval", - "type": "number" + "title": "blacklist" }, "maxPollRetries": { "default": 5, @@ -1303,40 +1228,36 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "uriBlacklist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriBlacklist", - "type": "array" - }, - "uriWhitelist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriWhitelist", - "type": "array" - }, - "url": { - "default": "ws://localhost:6680/mopidy/ws/", - "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/`", + "whitelist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", "examples": [ - "ws://localhost:6680/mopidy/ws/" + [ + "spotify", + "vlc" + ] ], - "title": "url", - "type": "string" + "title": "whitelist" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", + "title": "MPRISData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig": { + "MPRISSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1353,7 +1274,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", + "$ref": "#/definitions/MPRISData", "title": "data" }, "enable": { @@ -1371,12 +1292,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "mopidy" + "mpris" ], "title": "type", "type": "string" @@ -1386,31 +1307,45 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig", + "title": "MPRISSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData": { + "MopidyData": { "properties": { - "blacklist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } + "albumBlacklist": { + "default": [ + "Soundcloud" ], - "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", + "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": [ [ - "spotify", - "vlc" + "Soundcloud", + "Mixcloud" ] ], - "title": "blacklist" + "items": { + "type": "string" + }, + "title": "albumBlacklist", + "type": "array" + }, + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", + "examples": [ + 10 + ], + "title": "interval", + "type": "number" + }, + "maxInterval": { + "default": 30, + "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)", + "examples": [ + 30 + ], + "title": "maxInterval", + "type": "number" }, "maxPollRetries": { "default": 5, @@ -1444,36 +1379,40 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "whitelist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } - ], - "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", + "uriBlacklist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriBlacklist", + "type": "array" + }, + "uriWhitelist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriWhitelist", + "type": "array" + }, + "url": { + "default": "ws://localhost:6680/mopidy/ws/", + "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": [ - [ - "spotify", - "vlc" - ] + "ws://localhost:6680/mopidy/ws/" ], - "title": "whitelist" + "title": "url", + "type": "string" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", + "title": "MopidyData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig": { + "MopidySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1490,7 +1429,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", + "$ref": "#/definitions/MopidyData", "title": "data" }, "enable": { @@ -1508,12 +1447,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "mpris" + "mopidy" ], "title": "type", "type": "string" @@ -1523,10 +1462,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig", + "title": "MopidySourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig": { + "PlexSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1543,7 +1482,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", + "$ref": "#/definitions/PlexSourceData", "title": "data" }, "enable": { @@ -1561,7 +1500,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -1576,10 +1515,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig", + "title": "PlexSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData": { + "PlexSourceData": { "properties": { "libraries": { "anyOf": [ @@ -1650,7 +1589,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1696,60 +1635,121 @@ "title": "user" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", + "title": "PlexSourceData", + "type": "object" + }, + "Record": { + "title": "Record", + "type": "object" + }, + "ScrobbleThresholds": { + "properties": { + "duration": { + "default": 240, + "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", + "examples": [ + 240 + ], + "title": "duration", + "type": "number" + }, + "percent": { + "default": 50, + "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.", + "examples": [ + 50 + ], + "title": "percent", + "type": "number" + } + }, + "title": "ScrobbleThresholds", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig": { + "SourceAIOConfig": { "anyOf": [ { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig" + "$ref": "#/definitions/SpotifySourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig" + "$ref": "#/definitions/PlexSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig" + "$ref": "#/definitions/TautulliSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig" + "$ref": "#/definitions/DeezerSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig" + "$ref": "#/definitions/SubsonicSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig" + "$ref": "#/definitions/JellySourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig" + "$ref": "#/definitions/LastFmSouceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig" + "$ref": "#/definitions/YTMusicSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig" + "$ref": "#/definitions/MPRISSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig" + "$ref": "#/definitions/MopidySourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig" + "$ref": "#/definitions/ListenBrainzSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig" + "$ref": "#/definitions/JRiverSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig" + "$ref": "#/definitions/KodiSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig" + "$ref": "#/definitions/WebScrobblerSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig" + "$ref": "#/definitions/ChromecastSourceAIOConfig" } ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig" + "title": "SourceAIOConfig" + }, + "SourceRetryOptions": { + "properties": { + "maxPollRetries": { + "default": 5, + "description": "default # of automatic polling restarts on error", + "examples": [ + 5 + ], + "title": "maxPollRetries", + "type": "number" + }, + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "examples": [ + 1.5 + ], + "title": "retryMultiplier", + "type": "number" + } + }, + "title": "SourceRetryOptions", + "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig": { + "SpotifySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1766,7 +1766,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", + "$ref": "#/definitions/SpotifySourceData", "title": "data" }, "enable": { @@ -1784,7 +1784,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -1799,10 +1799,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig", + "title": "SpotifySourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData": { + "SpotifySourceData": { "properties": { "clientId": { "description": "spotify client id", @@ -1879,7 +1879,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -1889,10 +1889,10 @@ "clientSecret", "redirectUri" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", + "title": "SpotifySourceData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData": { + "SubsonicData": { "properties": { "ignoreTlsErrors": { "default": false, @@ -1964,7 +1964,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1987,10 +1987,10 @@ "url", "user" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", + "title": "SubsonicData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig": { + "SubsonicSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2007,7 +2007,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", + "$ref": "#/definitions/SubsonicData", "title": "data" }, "enable": { @@ -2025,7 +2025,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -2040,10 +2040,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig", + "title": "SubsonicSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig": { + "TautulliSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2060,7 +2060,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", + "$ref": "#/definitions/PlexSourceData", "title": "data" }, "enable": { @@ -2078,7 +2078,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -2093,10 +2093,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig", + "title": "TautulliSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData": { + "WebScrobblerData": { "properties": { "blacklist": { "anyOf": [ @@ -2175,7 +2175,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -2207,10 +2207,10 @@ "title": "whitelist" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", + "title": "WebScrobblerData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig": { + "WebScrobblerSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2227,7 +2227,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", + "$ref": "#/definitions/WebScrobblerData", "title": "data" }, "enable": { @@ -2245,7 +2245,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -2259,10 +2259,10 @@ "required": [ "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig", + "title": "WebScrobblerSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData": { + "YTMusicData": { "properties": { "authUser": { "description": "If the 'X-Goog-AuthUser' header is present in the Request Headers for music.youtube.com it must also be included", @@ -2327,7 +2327,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -2335,10 +2335,10 @@ "required": [ "cookie" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", + "title": "YTMusicData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig": { + "YTMusicSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2355,7 +2355,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", + "$ref": "#/definitions/YTMusicData", "title": "data" }, "enable": { @@ -2373,7 +2373,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -2388,18 +2388,18 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig", + "title": "YTMusicSourceAIOConfig", "type": "object" } }, "properties": { "sourceDefaults": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).SourceRetryOptions", + "$ref": "#/definitions/SourceRetryOptions", "title": "sourceDefaults" }, "sources": { "items": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig" + "$ref": "#/definitions/SourceAIOConfig" }, "title": "sources", "type": "array" diff --git a/src/backend/common/schema/aio.json b/src/backend/common/schema/aio.json index 4bf0612f..3a57e103 100644 --- a/src/backend/common/schema/aio.json +++ b/src/backend/common/schema/aio.json @@ -1,64 +1,82 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "LogLevel": { - "enum": [ - "debug", - "error", - "info", - "verbose", - "warn" - ], - "title": "LogLevel", - "type": "string" - }, - "Record": { - "title": "Record", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/Atomic\",{assert:{\"resolution-mode\":\"import\"}}).LogOptions": { + "ChromecastData": { "properties": { - "console": { - "description": "Specify the minimum log level streamed to the console (or docker container)", - "enum": [ - "debug", - "error", - false, - "info", - "verbose", - "warn" + "allowUnknownMedia": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "boolean" + } ], - "title": "console" + "default": false, + "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", + "title": "allowUnknownMedia" }, - "file": { - "description": "Specify the minimum log level to output to rotating files. If `false` no log files will be created.", - "enum": [ - "debug", - "error", - false, - "info", - "verbose", - "warn" + "blacklistApps": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } ], - "title": "file" + "description": "DO NOT scrobble from any application that START WITH these values, case-insensitive", + "examples": [ + [ + "spotify", + "pandora" + ] + ], + "title": "blacklistApps" }, - "level": { - "$ref": "#/definitions/LogLevel", - "default": "'info'", - "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.", - "title": "level" + "blacklistDevices": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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" + ] + ], + "title": "blacklistDevices" + }, + "devices": { + "description": "A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set", + "items": { + "$ref": "#/definitions/ChromecastDeviceInfo" + }, + "title": "devices", + "type": "array" + }, + "forceMediaRecognitionOn": { + "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", + "items": { + "type": "string" + }, + "title": "forceMediaRecognitionOn", + "type": "array" }, - "stream": { - "$ref": "#/definitions/LogLevel", - "description": "Specify the minimum log level streamed to the UI", - "title": "stream" - } - }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/Atomic\",{assert:{\"resolution-mode\":\"import\"}}).LogOptions", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/aioConfig\",{assert:{\"resolution-mode\":\"import\"}}).SourceDefaults": { - "properties": { "maxPollRetries": { "default": 5, "description": "default # of automatic polling restarts on error", @@ -78,7 +96,7 @@ "type": "number" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/Record", "title": "options" }, "retryMultiplier": { @@ -91,29 +109,161 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" + }, + "useAutoDiscovery": { + "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", + "title": "useAutoDiscovery", + "type": "boolean" + }, + "useAvahi": { + "default": false, + "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.", + "title": "useAvahi", + "type": "boolean" + }, + "whitelistApps": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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" + ] + ], + "title": "whitelistApps" + }, + "whitelistDevices": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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" + ] + ], + "title": "whitelistDevices" + } + }, + "title": "ChromecastData", + "type": "object" + }, + "ChromecastDeviceInfo": { + "properties": { + "address": { + "description": "The IP address of the device", + "examples": [ + "192.168.0.115" + ], + "title": "address", + "type": "string" + }, + "name": { + "description": "A friendly name to identify this device", + "examples": [ + "MySmartTV" + ], + "title": "name", + "type": "string" + } + }, + "required": [ + "address", + "name" + ], + "title": "ChromecastDeviceInfo", + "type": "object" + }, + "ChromecastSourceAIOConfig": { + "properties": { + "clients": { + "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", + "examples": [ + [ + "MyMalojaConfigName", + "MyLastFMConfigName" + ] + ], + "items": { + "type": "string" + }, + "title": "clients", + "type": "array" + }, + "data": { + "$ref": "#/definitions/ChromecastData", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", + "examples": [ + true + ], + "title": "enable", + "type": "boolean" + }, + "name": { + "description": "Unique identifier for this source.", + "title": "name", + "type": "string" + }, + "options": { + "$ref": "#/definitions/CommonSourceOptions", + "title": "options" + }, + "type": { + "enum": [ + "chromecast" + ], + "title": "type", + "type": "string" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/aioConfig\",{assert:{\"resolution-mode\":\"import\"}}).SourceDefaults", + "required": [ + "data", + "type" + ], + "title": "ChromecastSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig": { + "ClientAIOConfig": { "anyOf": [ { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig" + "$ref": "#/definitions/LastfmClientAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig" + "$ref": "#/definitions/ListenBrainzClientAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig" + "$ref": "#/definitions/MalojaClientAIOConfig" } ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig" + "title": "ClientAIOConfig" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData": { + "CommonClientData": { "properties": { "maxRequestRetries": { "default": 1, @@ -157,7 +307,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", + "$ref": "#/definitions/MatchLoggingOptions", "title": "match" } }, @@ -178,112 +328,91 @@ "type": "number" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData", + "title": "CommonClientData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions": { - "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", + "CommonSourceOptions": { "properties": { - "confidenceBreakdown": { - "default": false, - "description": "Include confidence breakdowns in track match logging, if applicable", + "logFilterFailure": { + "default": "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.", + "enum": [ + "debug", + false, + "warn" + ], + "examples": [ + "warn" + ], + "title": "logFilterFailure" + }, + "logPayload": { + "default": false, + "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", "examples": [ false ], - "title": "confidenceBreakdown", + "title": "logPayload", "type": "boolean" }, - "onMatch": { + "logPlayerState": { "default": false, - "description": "Log to DEBUG when a new track DOES match an existing scrobble", + "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", "examples": [ false ], - "title": "onMatch", + "title": "logPlayerState", "type": "boolean" }, - "onNoMatch": { - "default": false, - "description": "Log to DEBUG when a new track does NOT match an existing scrobble", + "scrobbleBacklog": { + "default": true, + "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", "examples": [ + true, false ], - "title": "onNoMatch", + "title": "scrobbleBacklog", "type": "boolean" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", + "title": "CommonSourceOptions", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig": { + "DeezerData": { "properties": { - "configureAs": { - "default": "client", - "description": "Should always be `client` when using LastFM as a client", - "enum": [ - "client", - "source" - ], + "clientId": { + "description": "deezer client id", "examples": [ - "client" + "a89cba1569901a0671d5a9875fed4be1" ], - "title": "configureAs", + "title": "clientId", "type": "string" }, - "data": { - "allOf": [ - { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData" - }, - { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData" - } - ], - "description": "Specific data required to configure this client", - "title": "data" - }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", + "clientSecret": { + "description": "deezer client secret", "examples": [ - true + "ec42e09d5ae0ee0f0816ca151008412a" ], - "title": "enable", - "type": "boolean" + "title": "clientSecret", + "type": "string" }, - "name": { - "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", + "interval": { + "default": 60, + "description": "optional, how long to wait before calling spotify for new tracks (in seconds)", "examples": [ - "MyConfig" + 60 ], - "title": "name", - "type": "string" + "title": "interval", + "type": "number" }, - "type": { - "enum": [ - "lastfm" - ], - "title": "type", - "type": "string" - } - }, - "required": [ - "data", - "name", - "type" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientAIOConfig", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData": { - "properties": { - "apiKey": { - "description": "API Key generated from Last.fm account", + "maxPollRetries": { + "default": 5, + "description": "default # of automatic polling restarts on error", "examples": [ - "787c921a2a2ab42320831aba0c8f2fc2" + 5 ], - "title": "apiKey", - "type": "string" + "title": "maxPollRetries", + "type": "number" }, "maxRequestRetries": { "default": 1, @@ -294,11 +423,15 @@ "title": "maxRequestRetries", "type": "number" }, + "options": { + "$ref": "#/definitions/Record", + "title": "options" + }, "redirectUri": { - "default": "http://localhost:9078/lastfm/callback", - "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/deezer/callback", + "description": "deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"", "examples": [ - "http://localhost:9078/lastfm/callback" + "http://localhost:9078/deezer/callback" ], "title": "redirectUri", "type": "string" @@ -312,45 +445,38 @@ "title": "retryMultiplier", "type": "number" }, - "secret": { - "description": "Secret generated from Last.fm account", - "examples": [ - "ec42e09d5ae0ee0f0816ca151008412a" - ], - "title": "secret", - "type": "string" - }, - "session": { - "description": "Optional session id returned from a completed auth flow", - "title": "session", - "type": "string" + "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.", + "title": "scrobbleThresholds" } }, "required": [ - "apiKey", - "secret" + "clientId", + "clientSecret", + "redirectUri" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData", + "title": "DeezerData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig": { + "DeezerSourceAIOConfig": { "properties": { - "configureAs": { - "default": "client", - "description": "Should always be `client` when using Listenbrainz as a client", - "enum": [ - "client", - "source" - ], + "clients": { + "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", "examples": [ - "client" + [ + "MyMalojaConfigName", + "MyLastFMConfigName" + ] ], - "title": "configureAs", - "type": "string" + "items": { + "type": "string" + }, + "title": "clients", + "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", - "description": "Specific data required to configure this client", + "$ref": "#/definitions/DeezerData", "title": "data" }, "enable": { @@ -363,16 +489,17 @@ "type": "boolean" }, "name": { - "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", - "examples": [ - "MyConfig" - ], + "description": "Unique identifier for this source.", "title": "name", "type": "string" }, + "options": { + "$ref": "#/definitions/CommonSourceOptions", + "title": "options" + }, "type": { "enum": [ - "listenbrainz" + "deezer" ], "title": "type", "type": "string" @@ -380,481 +507,79 @@ }, "required": [ "data", - "name", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientAIOConfig", + "title": "DeezerSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData": { + "GotifyConfig": { "properties": { - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", - "examples": [ - 1 - ], - "title": "maxRequestRetries", - "type": "number" + "name": { + "description": "A friendly name used to identify webhook config in logs", + "title": "name", + "type": "string" }, - "options": { - "properties": { - "checkExistingScrobbles": { - "default": true, - "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.", - "examples": [ - true - ], - "title": "checkExistingScrobbles", - "type": "boolean" - }, - "deadLetterRetries": { - "default": 1, - "description": "Number of times MS should automatically retry scrobbles in dead letter queue", - "examples": [ - 1 - ], - "title": "deadLetterRetries", - "type": "number" - }, - "refreshEnabled": { - "default": true, - "description": "Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history", - "examples": [ - true - ], - "title": "refreshEnabled", - "type": "boolean" - }, - "verbose": { - "description": "Options used for increasing verbosity of logging in MS (used for debugging)", - "properties": { - "match": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", - "title": "match" - } - }, - "title": "verbose", - "type": "object" - } - }, - "title": "options", - "type": "object" + "priorities": { + "$ref": "#/definitions/PrioritiesConfig", + "description": "Priority of messages\n\n* Info -> 5\n* Warn -> 7\n* Error -> 10", + "title": "priorities" }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "token": { + "description": "The token created for this Application in Gotify", "examples": [ - 1.5 + "AQZI58fA.rfSZbm" ], - "title": "retryMultiplier", - "type": "number" + "title": "token", + "type": "string" }, - "token": { - "description": "User token for the user to scrobble for", + "type": { + "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", + "enum": [ + "gotify", + "ntfy" + ], "examples": [ - "6794186bf-1157-4de6-80e5-uvb411f3ea2b" + "gotify" ], - "title": "token", + "title": "type", "type": "string" }, "url": { - "default": "https://api.listenbrainz.org/", - "description": "URL for the ListenBrainz server, if not using the default", + "description": "The URL of the Gotify server. Same URL that would be used to reach the Gotify UI", "examples": [ - "https://api.listenbrainz.org/" + "http://192.168.0.100:8078" ], "title": "url", "type": "string" - }, - "username": { - "description": "Username of the user to scrobble for", - "title": "username", - "type": "string" } }, "required": [ "token", - "username" + "type", + "url" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", + "title": "GotifyConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig": { + "JRiverData": { "properties": { - "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", - "description": "Specific data required to configure this client", - "title": "data" - }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", - "examples": [ - true - ], - "title": "enable", - "type": "boolean" - }, - "name": { - "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", - "examples": [ - "MyConfig" - ], - "title": "name", - "type": "string" - }, - "type": { - "enum": [ - "maloja" - ], - "title": "type", - "type": "string" - } - }, - "required": [ - "data", - "name", - "type" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientAIOConfig", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData": { - "properties": { - "apiKey": { - "description": "API Key for Maloja server", - "examples": [ - "myApiKey" - ], - "title": "apiKey", - "type": "string" - }, - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", - "examples": [ - 1 - ], - "title": "maxRequestRetries", - "type": "number" - }, - "options": { - "properties": { - "checkExistingScrobbles": { - "default": true, - "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.", - "examples": [ - true - ], - "title": "checkExistingScrobbles", - "type": "boolean" - }, - "deadLetterRetries": { - "default": 1, - "description": "Number of times MS should automatically retry scrobbles in dead letter queue", - "examples": [ - 1 - ], - "title": "deadLetterRetries", - "type": "number" - }, - "refreshEnabled": { - "default": true, - "description": "Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history", - "examples": [ - true - ], - "title": "refreshEnabled", - "type": "boolean" - }, - "verbose": { - "description": "Options used for increasing verbosity of logging in MS (used for debugging)", - "properties": { - "match": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", - "title": "match" - } - }, - "title": "verbose", - "type": "object" - } - }, - "title": "options", - "type": "object" - }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", - "examples": [ - 1.5 - ], - "title": "retryMultiplier", - "type": "number" - }, - "url": { - "description": "URL for maloja server", - "examples": [ - "http://localhost:42010" - ], - "title": "url", - "type": "string" - } - }, - "required": [ - "apiKey", - "url" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions": { - "properties": { - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", - "examples": [ - 1 - ], - "title": "maxRequestRetries", - "type": "number" - }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", - "examples": [ - 1.5 - ], - "title": "retryMultiplier", - "type": "number" - } - }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).GotifyConfig": { - "properties": { - "name": { - "description": "A friendly name used to identify webhook config in logs", - "title": "name", - "type": "string" - }, - "priorities": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).PrioritiesConfig", - "description": "Priority of messages\n\n* Info -> 5\n* Warn -> 7\n* Error -> 10", - "title": "priorities" - }, - "token": { - "description": "The token created for this Application in Gotify", - "examples": [ - "AQZI58fA.rfSZbm" - ], - "title": "token", - "type": "string" - }, - "type": { - "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", - "enum": [ - "gotify", - "ntfy" - ], - "examples": [ - "gotify" - ], - "title": "type", - "type": "string" - }, - "url": { - "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" - ], - "title": "url", - "type": "string" - } - }, - "required": [ - "token", - "type", - "url" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).GotifyConfig", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).NtfyConfig": { - "properties": { - "name": { - "description": "A friendly name used to identify webhook config in logs", - "title": "name", - "type": "string" - }, - "password": { - "description": "Required if topic is protected", - "title": "password", - "type": "string" - }, - "priorities": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).PrioritiesConfig", - "description": "Priority of messages\n\n* Info -> 3\n* Warn -> 4\n* Error -> 5", - "title": "priorities" - }, - "topic": { - "description": "The topic mutli-scrobbler should POST to", - "title": "topic", - "type": "string" - }, - "type": { - "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", - "enum": [ - "gotify", - "ntfy" - ], - "examples": [ - "gotify" - ], - "title": "type", - "type": "string" - }, - "url": { - "description": "The URL of the Ntfy server", - "examples": [ - "http://192.168.0.100:8078" - ], - "title": "url", - "type": "string" - }, - "username": { - "description": "Required if topic is protected", - "title": "username", - "type": "string" - } - }, - "required": [ - "topic", - "type", - "url" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).NtfyConfig", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).PrioritiesConfig": { - "properties": { - "error": { - "examples": [ - 10 - ], - "title": "error", - "type": "number" - }, - "info": { - "examples": [ - 5 - ], - "title": "info", - "type": "number" - }, - "warn": { - "examples": [ - 7 - ], - "title": "warn", - "type": "number" - } - }, - "required": [ - "error", - "info", - "warn" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).PrioritiesConfig", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).WebhookConfig": { - "anyOf": [ - { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).GotifyConfig" - }, - { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).NtfyConfig" - } - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).WebhookConfig" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData": { - "properties": { - "allowUnknownMedia": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "boolean" - } - ], - "default": false, - "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", - "title": "allowUnknownMedia" - }, - "blacklistApps": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } - ], - "description": "DO NOT scrobble from any application that START WITH these values, case-insensitive", + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", "examples": [ - [ - "spotify", - "pandora" - ] + 10 ], - "title": "blacklistApps" + "title": "interval", + "type": "number" }, - "blacklistDevices": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } - ], - "description": "DO NOT scrobble from any cast devices that START WITH these values, case-insensitive\n\nUseful when used with auto discovery", + "maxInterval": { + "default": 30, + "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)", "examples": [ - [ - "home-mini", - "family-tv" - ] + 30 ], - "title": "blacklistDevices" - }, - "devices": { - "description": "A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set", - "items": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo" - }, - "title": "devices", - "type": "array" - }, - "forceMediaRecognitionOn": { - "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", - "items": { - "type": "string" - }, - "title": "forceMediaRecognitionOn", - "type": "array" + "title": "maxInterval", + "type": "number" }, "maxPollRetries": { "default": 5, @@ -878,6 +603,11 @@ "$ref": "#/definitions/Record", "title": "options" }, + "password": { + "description": "If you have enabled authentication, the password you set", + "title": "password", + "type": "string" + }, "retryMultiplier": { "default": 1.5, "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", @@ -888,228 +618,32 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "useAutoDiscovery": { - "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", - "title": "useAutoDiscovery", - "type": "boolean" - }, - "useAvahi": { - "default": false, - "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.", - "title": "useAvahi", - "type": "boolean" - }, - "whitelistApps": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "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" - ] - ], - "title": "whitelistApps" - }, - "whitelistDevices": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "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" - ] - ], - "title": "whitelistDevices" - } - }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo": { - "properties": { - "address": { - "description": "The IP address of the device", - "examples": [ - "192.168.0.115" - ], - "title": "address", - "type": "string" - }, - "name": { - "description": "A friendly name to identify this device", - "examples": [ - "MySmartTV" - ], - "title": "name", - "type": "string" - } - }, - "required": [ - "address", - "name" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig": { - "properties": { - "clients": { - "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", - "examples": [ - [ - "MyMalojaConfigName", - "MyLastFMConfigName" - ] - ], - "items": { - "type": "string" - }, - "title": "clients", - "type": "array" - }, - "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", - "title": "data" - }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", - "examples": [ - true - ], - "title": "enable", - "type": "boolean" - }, - "name": { - "description": "Unique identifier for this source.", - "title": "name", - "type": "string" - }, - "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", - "title": "options" - }, - "type": { - "enum": [ - "chromecast" - ], - "title": "type", - "type": "string" - } - }, - "required": [ - "data", - "type" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData": { - "properties": { - "clientId": { - "description": "deezer client id", - "examples": [ - "a89cba1569901a0671d5a9875fed4be1" - ], - "title": "clientId", - "type": "string" - }, - "clientSecret": { - "description": "deezer client secret", - "examples": [ - "ec42e09d5ae0ee0f0816ca151008412a" - ], - "title": "clientSecret", - "type": "string" - }, - "interval": { - "default": 60, - "description": "optional, how long to wait before calling spotify for new tracks (in seconds)", - "examples": [ - 60 - ], - "title": "interval", - "type": "number" - }, - "maxPollRetries": { - "default": 5, - "description": "default # of automatic polling restarts on error", - "examples": [ - 5 - ], - "title": "maxPollRetries", - "type": "number" - }, - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", - "examples": [ - 1 - ], - "title": "maxRequestRetries", - "type": "number" - }, - "options": { - "$ref": "#/definitions/Record", - "title": "options" - }, - "redirectUri": { - "default": "http://localhost:9078/deezer/callback", - "description": "deezer redirect URI -- required only if not the default shown here. URI must end in \"callback\"", + "url": { + "default": "http://localhost:52199/MCWS/v1/", + "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:9078/deezer/callback" + "http://localhost:52199/MCWS/v1/" ], - "title": "redirectUri", + "title": "url", "type": "string" }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", - "examples": [ - 1.5 - ], - "title": "retryMultiplier", - "type": "number" - }, - "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", - "title": "scrobbleThresholds" + "username": { + "description": "If you have enabled authentication, the username you set", + "title": "username", + "type": "string" } }, "required": [ - "clientId", - "clientSecret", - "redirectUri" + "url" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", + "title": "JRiverData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig": { + "JRiverSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1126,7 +660,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", + "$ref": "#/definitions/JRiverData", "title": "data" }, "enable": { @@ -1144,12 +678,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "deezer" + "jriver" ], "title": "type", "type": "string" @@ -1159,81 +693,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions": { - "properties": { - "logFilterFailure": { - "default": "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.", - "enum": [ - "debug", - false, - "warn" - ], - "examples": [ - "warn" - ], - "title": "logFilterFailure" - }, - "logPayload": { - "default": false, - "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", - "examples": [ - false - ], - "title": "logPayload", - "type": "boolean" - }, - "logPlayerState": { - "default": false, - "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", - "examples": [ - false - ], - "title": "logPlayerState", - "type": "boolean" - }, - "scrobbleBacklog": { - "default": true, - "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", - "examples": [ - true, - false - ], - "title": "scrobbleBacklog", - "type": "boolean" - } - }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds": { - "properties": { - "duration": { - "default": 240, - "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", - "examples": [ - 240 - ], - "title": "duration", - "type": "number" - }, - "percent": { - "default": 50, - "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.", - "examples": [ - 50 - ], - "title": "percent", - "type": "number" - } - }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds", + "title": "JRiverSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData": { + "JellyData": { "properties": { "maxPollRetries": { "default": 5, @@ -1292,7 +755,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1338,10 +801,10 @@ "title": "users" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", + "title": "JellyData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig": { + "JellySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1358,7 +821,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", + "$ref": "#/definitions/JellyData", "title": "data" }, "enable": { @@ -1376,7 +839,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -1391,10 +854,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig", + "title": "JellySourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData": { + "KodiData": { "properties": { "interval": { "default": 10, @@ -1437,7 +900,7 @@ "title": "options" }, "password": { - "description": "If you have enabled authentication, the password you set", + "description": "The password set for Remote Control via Web Sever", "title": "password", "type": "string" }, @@ -1451,32 +914,34 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, "url": { - "default": "http://localhost:52199/MCWS/v1/", - "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/`", + "default": "http://localhost:8080/jsonrpc", + "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:52199/MCWS/v1/" + "http://localhost:8080/jsonrpc" ], "title": "url", "type": "string" }, "username": { - "description": "If you have enabled authentication, the username you set", + "description": "The username set for Remote Control via Web Sever", "title": "username", "type": "string" } }, "required": [ - "url" + "password", + "url", + "username" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", + "title": "KodiData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig": { + "KodiSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1493,7 +958,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", + "$ref": "#/definitions/KodiData", "title": "data" }, "enable": { @@ -1511,12 +976,77 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "jriver" + "kodi" + ], + "title": "type", + "type": "string" + } + }, + "required": [ + "data", + "type" + ], + "title": "KodiSourceAIOConfig", + "type": "object" + }, + "LastFmSouceAIOConfig": { + "properties": { + "clients": { + "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", + "examples": [ + [ + "MyMalojaConfigName", + "MyLastFMConfigName" + ] + ], + "items": { + "type": "string" + }, + "title": "clients", + "type": "array" + }, + "configureAs": { + "default": "source", + "description": "When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.", + "enum": [ + "source" + ], + "examples": [ + "source" + ], + "title": "configureAs", + "type": "string" + }, + "data": { + "$ref": "#/definitions/LastFmSourceData", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", + "examples": [ + true + ], + "title": "enable", + "type": "boolean" + }, + "name": { + "description": "Unique identifier for this source.", + "title": "name", + "type": "string" + }, + "options": { + "$ref": "#/definitions/CommonSourceOptions", + "title": "options" + }, + "type": { + "enum": [ + "lastfm" ], "title": "type", "type": "string" @@ -1526,11 +1056,19 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig", + "title": "LastFmSouceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData": { + "LastFmSourceData": { "properties": { + "apiKey": { + "description": "API Key generated from Last.fm account", + "examples": [ + "787c921a2a2ab42320831aba0c8f2fc2" + ], + "title": "apiKey", + "type": "string" + }, "interval": { "default": 10, "description": "How long to wait before polling the source API for new tracks (in seconds)", @@ -1571,9 +1109,13 @@ "$ref": "#/definitions/Record", "title": "options" }, - "password": { - "description": "The password set for Remote Control via Web Sever", - "title": "password", + "redirectUri": { + "default": "http://localhost:9078/lastfm/callback", + "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.", + "examples": [ + "http://localhost:9078/lastfm/callback" + ], + "title": "redirectUri", "type": "string" }, "retryMultiplier": { @@ -1586,116 +1128,56 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "url": { - "default": "http://localhost:8080/jsonrpc", - "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" - ], - "title": "url", - "type": "string" - }, - "username": { - "description": "The username set for Remote Control via Web Sever", - "title": "username", - "type": "string" - } - }, - "required": [ - "password", - "url", - "username" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig": { - "properties": { - "clients": { - "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", - "examples": [ - [ - "MyMalojaConfigName", - "MyLastFMConfigName" - ] - ], - "items": { - "type": "string" - }, - "title": "clients", - "type": "array" - }, - "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", - "title": "data" - }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", + "secret": { + "description": "Secret generated from Last.fm account", "examples": [ - true + "ec42e09d5ae0ee0f0816ca151008412a" ], - "title": "enable", - "type": "boolean" - }, - "name": { - "description": "Unique identifier for this source.", - "title": "name", + "title": "secret", "type": "string" }, - "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", - "title": "options" - }, - "type": { - "enum": [ - "kodi" - ], - "title": "type", + "session": { + "description": "Optional session id returned from a completed auth flow", + "title": "session", "type": "string" } }, "required": [ - "data", - "type" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig": { - "properties": { - "clients": { - "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", - "examples": [ - [ - "MyMalojaConfigName", - "MyLastFMConfigName" - ] - ], - "items": { - "type": "string" - }, - "title": "clients", - "type": "array" - }, + "apiKey", + "secret" + ], + "title": "LastFmSourceData", + "type": "object" + }, + "LastfmClientAIOConfig": { + "properties": { "configureAs": { - "default": "source", - "description": "When used in `lastfm.config` this tells multi-scrobbler whether to use this data to configure a source or client.", + "default": "client", + "description": "Should always be `client` when using LastFM as a client", "enum": [ + "client", "source" ], "examples": [ - "source" + "client" ], "title": "configureAs", "type": "string" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", + "allOf": [ + { + "$ref": "#/definitions/CommonClientData" + }, + { + "$ref": "#/definitions/LastfmData" + } + ], + "description": "Specific data required to configure this client", "title": "data" }, "enable": { @@ -1708,14 +1190,13 @@ "type": "boolean" }, "name": { - "description": "Unique identifier for this source.", + "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", + "examples": [ + "MyConfig" + ], "title": "name", "type": "string" }, - "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", - "title": "options" - }, "type": { "enum": [ "lastfm" @@ -1726,12 +1207,13 @@ }, "required": [ "data", + "name", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig", + "title": "LastfmClientAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData": { + "LastfmData": { "properties": { "apiKey": { "description": "API Key generated from Last.fm account", @@ -1741,33 +1223,6 @@ "title": "apiKey", "type": "string" }, - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 - ], - "title": "interval", - "type": "number" - }, - "maxInterval": { - "default": 30, - "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)", - "examples": [ - 30 - ], - "title": "maxInterval", - "type": "number" - }, - "maxPollRetries": { - "default": 5, - "description": "default # of automatic polling restarts on error", - "examples": [ - 5 - ], - "title": "maxPollRetries", - "type": "number" - }, "maxRequestRetries": { "default": 1, "description": "default # of http request retries a source can make before error is thrown", @@ -1777,10 +1232,6 @@ "title": "maxRequestRetries", "type": "number" }, - "options": { - "$ref": "#/definitions/Record", - "title": "options" - }, "redirectUri": { "default": "http://localhost:9078/lastfm/callback", "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.", @@ -1799,11 +1250,6 @@ "title": "retryMultiplier", "type": "number" }, - "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", - "title": "scrobbleThresholds" - }, "secret": { "description": "Secret generated from Last.fm account", "examples": [ @@ -1822,10 +1268,157 @@ "apiKey", "secret" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", + "title": "LastfmData", + "type": "object" + }, + "ListenBrainzClientAIOConfig": { + "properties": { + "configureAs": { + "default": "client", + "description": "Should always be `client` when using Listenbrainz as a client", + "enum": [ + "client", + "source" + ], + "examples": [ + "client" + ], + "title": "configureAs", + "type": "string" + }, + "data": { + "$ref": "#/definitions/ListenBrainzClientData", + "description": "Specific data required to configure this client", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", + "examples": [ + true + ], + "title": "enable", + "type": "boolean" + }, + "name": { + "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", + "examples": [ + "MyConfig" + ], + "title": "name", + "type": "string" + }, + "type": { + "enum": [ + "listenbrainz" + ], + "title": "type", + "type": "string" + } + }, + "required": [ + "data", + "name", + "type" + ], + "title": "ListenBrainzClientAIOConfig", + "type": "object" + }, + "ListenBrainzClientData": { + "properties": { + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" + }, + "options": { + "properties": { + "checkExistingScrobbles": { + "default": true, + "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.", + "examples": [ + true + ], + "title": "checkExistingScrobbles", + "type": "boolean" + }, + "deadLetterRetries": { + "default": 1, + "description": "Number of times MS should automatically retry scrobbles in dead letter queue", + "examples": [ + 1 + ], + "title": "deadLetterRetries", + "type": "number" + }, + "refreshEnabled": { + "default": true, + "description": "Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history", + "examples": [ + true + ], + "title": "refreshEnabled", + "type": "boolean" + }, + "verbose": { + "description": "Options used for increasing verbosity of logging in MS (used for debugging)", + "properties": { + "match": { + "$ref": "#/definitions/MatchLoggingOptions", + "title": "match" + } + }, + "title": "verbose", + "type": "object" + } + }, + "title": "options", + "type": "object" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "examples": [ + 1.5 + ], + "title": "retryMultiplier", + "type": "number" + }, + "token": { + "description": "User token for the user to scrobble for", + "examples": [ + "6794186bf-1157-4de6-80e5-uvb411f3ea2b" + ], + "title": "token", + "type": "string" + }, + "url": { + "default": "https://api.listenbrainz.org/", + "description": "URL for the ListenBrainz server, if not using the default", + "examples": [ + "https://api.listenbrainz.org/" + ], + "title": "url", + "type": "string" + }, + "username": { + "description": "Username of the user to scrobble for", + "title": "username", + "type": "string" + } + }, + "required": [ + "token", + "username" + ], + "title": "ListenBrainzClientData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig": { + "ListenBrainzSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1854,7 +1447,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", + "$ref": "#/definitions/ListenBrainzSourceData", "title": "data" }, "enable": { @@ -1872,7 +1465,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -1887,10 +1480,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig", + "title": "ListenBrainzSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData": { + "ListenBrainzSourceData": { "properties": { "interval": { "default": 10, @@ -1942,7 +1535,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1963,55 +1556,93 @@ "title": "url", "type": "string" }, - "username": { - "description": "Username of the user to scrobble for", - "title": "username", - "type": "string" + "username": { + "description": "Username of the user to scrobble for", + "title": "username", + "type": "string" + } + }, + "required": [ + "token", + "username" + ], + "title": "ListenBrainzSourceData", + "type": "object" + }, + "LogLevel": { + "enum": [ + "debug", + "error", + "info", + "verbose", + "warn" + ], + "title": "LogLevel", + "type": "string" + }, + "LogOptions": { + "properties": { + "console": { + "description": "Specify the minimum log level streamed to the console (or docker container)", + "enum": [ + "debug", + "error", + false, + "info", + "verbose", + "warn" + ], + "title": "console" + }, + "file": { + "description": "Specify the minimum log level to output to rotating files. If `false` no log files will be created.", + "enum": [ + "debug", + "error", + false, + "info", + "verbose", + "warn" + ], + "title": "file" + }, + "level": { + "$ref": "#/definitions/LogLevel", + "default": "'info'", + "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.", + "title": "level" + }, + "stream": { + "$ref": "#/definitions/LogLevel", + "description": "Specify the minimum log level streamed to the UI", + "title": "stream" } }, - "required": [ - "token", - "username" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", + "title": "LogOptions", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData": { + "MPRISData": { "properties": { - "albumBlacklist": { - "default": [ - "Soundcloud" + "blacklist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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.", + "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", "examples": [ [ - "Soundcloud", - "Mixcloud" + "spotify", + "vlc" ] ], - "items": { - "type": "string" - }, - "title": "albumBlacklist", - "type": "array" - }, - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 - ], - "title": "interval", - "type": "number" - }, - "maxInterval": { - "default": 30, - "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)", - "examples": [ - 30 - ], - "title": "maxInterval", - "type": "number" + "title": "blacklist" }, "maxPollRetries": { "default": 5, @@ -2045,40 +1676,36 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "uriBlacklist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriBlacklist", - "type": "array" - }, - "uriWhitelist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriWhitelist", - "type": "array" - }, - "url": { - "default": "ws://localhost:6680/mopidy/ws/", - "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/`", + "whitelist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", "examples": [ - "ws://localhost:6680/mopidy/ws/" + [ + "spotify", + "vlc" + ] ], - "title": "url", - "type": "string" + "title": "whitelist" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", + "title": "MPRISData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig": { + "MPRISSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2095,7 +1722,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", + "$ref": "#/definitions/MPRISData", "title": "data" }, "enable": { @@ -2113,12 +1740,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "mopidy" + "mpris" ], "title": "type", "type": "string" @@ -2128,31 +1755,207 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig", + "title": "MPRISSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData": { + "MalojaClientAIOConfig": { "properties": { - "blacklist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" + "data": { + "$ref": "#/definitions/MalojaClientData", + "description": "Specific data required to configure this client", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", + "examples": [ + true + ], + "title": "enable", + "type": "boolean" + }, + "name": { + "description": "Unique identifier for this client. Used with sources to restrict where scrobbles are sent.", + "examples": [ + "MyConfig" + ], + "title": "name", + "type": "string" + }, + "type": { + "enum": [ + "maloja" + ], + "title": "type", + "type": "string" + } + }, + "required": [ + "data", + "name", + "type" + ], + "title": "MalojaClientAIOConfig", + "type": "object" + }, + "MalojaClientData": { + "properties": { + "apiKey": { + "description": "API Key for Maloja server", + "examples": [ + "myApiKey" + ], + "title": "apiKey", + "type": "string" + }, + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" + }, + "options": { + "properties": { + "checkExistingScrobbles": { + "default": true, + "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.", + "examples": [ + true + ], + "title": "checkExistingScrobbles", + "type": "boolean" }, - { - "type": "string" + "deadLetterRetries": { + "default": 1, + "description": "Number of times MS should automatically retry scrobbles in dead letter queue", + "examples": [ + 1 + ], + "title": "deadLetterRetries", + "type": "number" + }, + "refreshEnabled": { + "default": true, + "description": "Try to get fresh scrobble history from client when tracks to be scrobbled are newer than the last scrobble found in client history", + "examples": [ + true + ], + "title": "refreshEnabled", + "type": "boolean" + }, + "verbose": { + "description": "Options used for increasing verbosity of logging in MS (used for debugging)", + "properties": { + "match": { + "$ref": "#/definitions/MatchLoggingOptions", + "title": "match" + } + }, + "title": "verbose", + "type": "object" } + }, + "title": "options", + "type": "object" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "examples": [ + 1.5 + ], + "title": "retryMultiplier", + "type": "number" + }, + "url": { + "description": "URL for maloja server", + "examples": [ + "http://localhost:42010" + ], + "title": "url", + "type": "string" + } + }, + "required": [ + "apiKey", + "url" + ], + "title": "MalojaClientData", + "type": "object" + }, + "MatchLoggingOptions": { + "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", + "properties": { + "confidenceBreakdown": { + "default": false, + "description": "Include confidence breakdowns in track match logging, if applicable", + "examples": [ + false + ], + "title": "confidenceBreakdown", + "type": "boolean" + }, + "onMatch": { + "default": false, + "description": "Log to DEBUG when a new track DOES match an existing scrobble", + "examples": [ + false + ], + "title": "onMatch", + "type": "boolean" + }, + "onNoMatch": { + "default": false, + "description": "Log to DEBUG when a new track does NOT match an existing scrobble", + "examples": [ + false + ], + "title": "onNoMatch", + "type": "boolean" + } + }, + "title": "MatchLoggingOptions", + "type": "object" + }, + "MopidyData": { + "properties": { + "albumBlacklist": { + "default": [ + "Soundcloud" ], - "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", + "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": [ [ - "spotify", - "vlc" + "Soundcloud", + "Mixcloud" ] ], - "title": "blacklist" + "items": { + "type": "string" + }, + "title": "albumBlacklist", + "type": "array" + }, + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", + "examples": [ + 10 + ], + "title": "interval", + "type": "number" + }, + "maxInterval": { + "default": 30, + "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)", + "examples": [ + 30 + ], + "title": "maxInterval", + "type": "number" }, "maxPollRetries": { "default": 5, @@ -2186,36 +1989,40 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "whitelist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } - ], - "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", + "uriBlacklist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriBlacklist", + "type": "array" + }, + "uriWhitelist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriWhitelist", + "type": "array" + }, + "url": { + "default": "ws://localhost:6680/mopidy/ws/", + "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": [ - [ - "spotify", - "vlc" - ] + "ws://localhost:6680/mopidy/ws/" ], - "title": "whitelist" + "title": "url", + "type": "string" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", + "title": "MopidyData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig": { + "MopidySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2232,7 +2039,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", + "$ref": "#/definitions/MopidyData", "title": "data" }, "enable": { @@ -2250,12 +2057,12 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { "enum": [ - "mpris" + "mopidy" ], "title": "type", "type": "string" @@ -2265,10 +2072,66 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig", + "title": "MopidySourceAIOConfig", + "type": "object" + }, + "NtfyConfig": { + "properties": { + "name": { + "description": "A friendly name used to identify webhook config in logs", + "title": "name", + "type": "string" + }, + "password": { + "description": "Required if topic is protected", + "title": "password", + "type": "string" + }, + "priorities": { + "$ref": "#/definitions/PrioritiesConfig", + "description": "Priority of messages\n\n* Info -> 3\n* Warn -> 4\n* Error -> 5", + "title": "priorities" + }, + "topic": { + "description": "The topic mutli-scrobbler should POST to", + "title": "topic", + "type": "string" + }, + "type": { + "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", + "enum": [ + "gotify", + "ntfy" + ], + "examples": [ + "gotify" + ], + "title": "type", + "type": "string" + }, + "url": { + "description": "The URL of the Ntfy server", + "examples": [ + "http://192.168.0.100:8078" + ], + "title": "url", + "type": "string" + }, + "username": { + "description": "Required if topic is protected", + "title": "username", + "type": "string" + } + }, + "required": [ + "topic", + "type", + "url" + ], + "title": "NtfyConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig": { + "PlexSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2285,7 +2148,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", + "$ref": "#/definitions/PlexSourceData", "title": "data" }, "enable": { @@ -2303,7 +2166,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -2318,10 +2181,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig", + "title": "PlexSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData": { + "PlexSourceData": { "properties": { "libraries": { "anyOf": [ @@ -2392,7 +2255,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -2438,60 +2301,186 @@ "title": "user" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", + "title": "PlexSourceData", + "type": "object" + }, + "PrioritiesConfig": { + "properties": { + "error": { + "examples": [ + 10 + ], + "title": "error", + "type": "number" + }, + "info": { + "examples": [ + 5 + ], + "title": "info", + "type": "number" + }, + "warn": { + "examples": [ + 7 + ], + "title": "warn", + "type": "number" + } + }, + "required": [ + "error", + "info", + "warn" + ], + "title": "PrioritiesConfig", + "type": "object" + }, + "Record": { + "title": "Record", + "type": "object" + }, + "RequestRetryOptions": { + "properties": { + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "examples": [ + 1.5 + ], + "title": "retryMultiplier", + "type": "number" + } + }, + "title": "RequestRetryOptions", + "type": "object" + }, + "ScrobbleThresholds": { + "properties": { + "duration": { + "default": 240, + "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", + "examples": [ + 240 + ], + "title": "duration", + "type": "number" + }, + "percent": { + "default": 50, + "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.", + "examples": [ + 50 + ], + "title": "percent", + "type": "number" + } + }, + "title": "ScrobbleThresholds", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig": { + "SourceAIOConfig": { "anyOf": [ { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig" + "$ref": "#/definitions/SpotifySourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceAIOConfig" + "$ref": "#/definitions/PlexSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig" + "$ref": "#/definitions/TautulliSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceAIOConfig" + "$ref": "#/definitions/DeezerSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig" + "$ref": "#/definitions/SubsonicSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceAIOConfig" + "$ref": "#/definitions/JellySourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSouceAIOConfig" + "$ref": "#/definitions/LastFmSouceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig" + "$ref": "#/definitions/YTMusicSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceAIOConfig" + "$ref": "#/definitions/MPRISSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceAIOConfig" + "$ref": "#/definitions/MopidySourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceAIOConfig" + "$ref": "#/definitions/ListenBrainzSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceAIOConfig" + "$ref": "#/definitions/JRiverSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceAIOConfig" + "$ref": "#/definitions/KodiSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig" + "$ref": "#/definitions/WebScrobblerSourceAIOConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceAIOConfig" + "$ref": "#/definitions/ChromecastSourceAIOConfig" } ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig" + "title": "SourceAIOConfig" + }, + "SourceDefaults": { + "properties": { + "maxPollRetries": { + "default": 5, + "description": "default # of automatic polling restarts on error", + "examples": [ + 5 + ], + "title": "maxPollRetries", + "type": "number" + }, + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", + "examples": [ + 1 + ], + "title": "maxRequestRetries", + "type": "number" + }, + "options": { + "$ref": "#/definitions/CommonSourceOptions", + "title": "options" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", + "examples": [ + 1.5 + ], + "title": "retryMultiplier", + "type": "number" + }, + "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.", + "title": "scrobbleThresholds" + } + }, + "title": "SourceDefaults", + "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig": { + "SpotifySourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2508,7 +2497,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", + "$ref": "#/definitions/SpotifySourceData", "title": "data" }, "enable": { @@ -2526,7 +2515,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -2541,10 +2530,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceAIOConfig", + "title": "SpotifySourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData": { + "SpotifySourceData": { "properties": { "clientId": { "description": "spotify client id", @@ -2621,7 +2610,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -2631,10 +2620,10 @@ "clientSecret", "redirectUri" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", + "title": "SpotifySourceData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData": { + "SubsonicData": { "properties": { "ignoreTlsErrors": { "default": false, @@ -2706,7 +2695,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -2729,10 +2718,10 @@ "url", "user" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", + "title": "SubsonicData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig": { + "SubsonicSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2749,7 +2738,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", + "$ref": "#/definitions/SubsonicData", "title": "data" }, "enable": { @@ -2767,7 +2756,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -2782,10 +2771,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicSourceAIOConfig", + "title": "SubsonicSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig": { + "TautulliSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2802,7 +2791,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", + "$ref": "#/definitions/PlexSourceData", "title": "data" }, "enable": { @@ -2820,7 +2809,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -2835,10 +2824,10 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceAIOConfig", + "title": "TautulliSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData": { + "WebScrobblerData": { "properties": { "blacklist": { "anyOf": [ @@ -2917,7 +2906,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -2949,10 +2938,10 @@ "title": "whitelist" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", + "title": "WebScrobblerData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig": { + "WebScrobblerSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2969,7 +2958,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", + "$ref": "#/definitions/WebScrobblerData", "title": "data" }, "enable": { @@ -2987,7 +2976,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -3001,10 +2990,21 @@ "required": [ "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceAIOConfig", + "title": "WebScrobblerSourceAIOConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData": { + "WebhookConfig": { + "anyOf": [ + { + "$ref": "#/definitions/GotifyConfig" + }, + { + "$ref": "#/definitions/NtfyConfig" + } + ], + "title": "WebhookConfig" + }, + "YTMusicData": { "properties": { "authUser": { "description": "If the 'X-Goog-AuthUser' header is present in the Request Headers for music.youtube.com it must also be included", @@ -3069,7 +3069,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -3077,10 +3077,10 @@ "required": [ "cookie" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", + "title": "YTMusicData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig": { + "YTMusicSourceAIOConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -3097,7 +3097,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", + "$ref": "#/definitions/YTMusicData", "title": "data" }, "enable": { @@ -3115,7 +3115,7 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" }, "type": { @@ -3130,7 +3130,7 @@ "data", "type" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceAIOConfig", + "title": "YTMusicSourceAIOConfig", "type": "object" } }, @@ -3147,12 +3147,12 @@ "type": "string" }, "clientDefaults": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/common\",{assert:{\"resolution-mode\":\"import\"}}).RequestRetryOptions", + "$ref": "#/definitions/RequestRetryOptions", "title": "clientDefaults" }, "clients": { "items": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/clients\",{assert:{\"resolution-mode\":\"import\"}}).ClientAIOConfig" + "$ref": "#/definitions/ClientAIOConfig" }, "title": "clients", "type": "array" @@ -3167,7 +3167,7 @@ "type": "boolean" }, "logging": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/Atomic\",{assert:{\"resolution-mode\":\"import\"}}).LogOptions", + "$ref": "#/definitions/LogOptions", "title": "logging" }, "port": { @@ -3180,19 +3180,19 @@ "type": "number" }, "sourceDefaults": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/aioConfig\",{assert:{\"resolution-mode\":\"import\"}}).SourceDefaults", + "$ref": "#/definitions/SourceDefaults", "title": "sourceDefaults" }, "sources": { "items": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/sources\",{assert:{\"resolution-mode\":\"import\"}}).SourceAIOConfig" + "$ref": "#/definitions/SourceAIOConfig" }, "title": "sources", "type": "array" }, "webhooks": { "items": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/health/webhooks\",{assert:{\"resolution-mode\":\"import\"}}).WebhookConfig" + "$ref": "#/definitions/WebhookConfig" }, "title": "webhooks", "type": "array" diff --git a/src/backend/common/schema/client.json b/src/backend/common/schema/client.json index 299f7c39..dc209586 100644 --- a/src/backend/common/schema/client.json +++ b/src/backend/common/schema/client.json @@ -2,17 +2,17 @@ "$schema": "http://json-schema.org/draft-07/schema#", "anyOf": [ { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientConfig" + "$ref": "#/definitions/LastfmClientConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientConfig" + "$ref": "#/definitions/ListenBrainzClientConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientConfig" + "$ref": "#/definitions/MalojaClientConfig" } ], "definitions": { - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData": { + "CommonClientData": { "properties": { "maxRequestRetries": { "default": 1, @@ -56,7 +56,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", + "$ref": "#/definitions/MatchLoggingOptions", "title": "match" } }, @@ -77,44 +77,10 @@ "type": "number" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData", + "title": "CommonClientData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions": { - "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", - "properties": { - "confidenceBreakdown": { - "default": false, - "description": "Include confidence breakdowns in track match logging, if applicable", - "examples": [ - false - ], - "title": "confidenceBreakdown", - "type": "boolean" - }, - "onMatch": { - "default": false, - "description": "Log to DEBUG when a new track DOES match an existing scrobble", - "examples": [ - false - ], - "title": "onMatch", - "type": "boolean" - }, - "onNoMatch": { - "default": false, - "description": "Log to DEBUG when a new track does NOT match an existing scrobble", - "examples": [ - false - ], - "title": "onNoMatch", - "type": "boolean" - } - }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientConfig": { + "LastfmClientConfig": { "properties": { "configureAs": { "default": "client", @@ -132,10 +98,10 @@ "data": { "allOf": [ { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonClientData" + "$ref": "#/definitions/CommonClientData" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData" + "$ref": "#/definitions/LastfmData" } ], "description": "Specific data required to configure this client", @@ -163,10 +129,10 @@ "data", "name" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmClientConfig", + "title": "LastfmClientConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData": { + "LastfmData": { "properties": { "apiKey": { "description": "API Key generated from Last.fm account", @@ -221,10 +187,10 @@ "apiKey", "secret" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmData", + "title": "LastfmData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientConfig": { + "ListenBrainzClientConfig": { "properties": { "configureAs": { "default": "client", @@ -240,7 +206,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", + "$ref": "#/definitions/ListenBrainzClientData", "description": "Specific data required to configure this client", "title": "data" }, @@ -266,10 +232,10 @@ "data", "name" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientConfig", + "title": "ListenBrainzClientConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData": { + "ListenBrainzClientData": { "properties": { "maxRequestRetries": { "default": 1, @@ -313,7 +279,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", + "$ref": "#/definitions/MatchLoggingOptions", "title": "match" } }, @@ -360,13 +326,13 @@ "token", "username" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzClientData", + "title": "ListenBrainzClientData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientConfig": { + "MalojaClientConfig": { "properties": { "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", + "$ref": "#/definitions/MalojaClientData", "description": "Specific data required to configure this client", "title": "data" }, @@ -392,10 +358,10 @@ "data", "name" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientConfig", + "title": "MalojaClientConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData": { + "MalojaClientData": { "properties": { "apiKey": { "description": "API Key for Maloja server", @@ -447,7 +413,7 @@ "description": "Options used for increasing verbosity of logging in MS (used for debugging)", "properties": { "match": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/index\",{assert:{\"resolution-mode\":\"import\"}}).MatchLoggingOptions", + "$ref": "#/definitions/MatchLoggingOptions", "title": "match" } }, @@ -480,7 +446,41 @@ "apiKey", "url" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/client/maloja\",{assert:{\"resolution-mode\":\"import\"}}).MalojaClientData", + "title": "MalojaClientData", + "type": "object" + }, + "MatchLoggingOptions": { + "description": "Scrobble matching (between new source track and existing client scrobbles) logging options. Used for debugging.", + "properties": { + "confidenceBreakdown": { + "default": false, + "description": "Include confidence breakdowns in track match logging, if applicable", + "examples": [ + false + ], + "title": "confidenceBreakdown", + "type": "boolean" + }, + "onMatch": { + "default": false, + "description": "Log to DEBUG when a new track DOES match an existing scrobble", + "examples": [ + false + ], + "title": "onMatch", + "type": "boolean" + }, + "onNoMatch": { + "default": false, + "description": "Log to DEBUG when a new track does NOT match an existing scrobble", + "examples": [ + false + ], + "title": "onNoMatch", + "type": "boolean" + } + }, + "title": "MatchLoggingOptions", "type": "object" } } diff --git a/src/backend/common/schema/source.json b/src/backend/common/schema/source.json index c602b5ae..3f4fdbb5 100644 --- a/src/backend/common/schema/source.json +++ b/src/backend/common/schema/source.json @@ -2,57 +2,53 @@ "$schema": "http://json-schema.org/draft-07/schema#", "anyOf": [ { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceConfig" + "$ref": "#/definitions/SpotifySourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceConfig" + "$ref": "#/definitions/PlexSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceConfig" + "$ref": "#/definitions/TautulliSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceConfig" + "$ref": "#/definitions/DeezerSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubSonicSourceConfig" + "$ref": "#/definitions/SubSonicSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceConfig" + "$ref": "#/definitions/JellySourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmSourceConfig" + "$ref": "#/definitions/LastfmSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceConfig" + "$ref": "#/definitions/YTMusicSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceConfig" + "$ref": "#/definitions/MPRISSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceConfig" + "$ref": "#/definitions/MopidySourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceConfig" + "$ref": "#/definitions/ListenBrainzSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceConfig" + "$ref": "#/definitions/JRiverSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceConfig" + "$ref": "#/definitions/KodiSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceConfig" + "$ref": "#/definitions/WebScrobblerSourceConfig" }, { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceConfig" + "$ref": "#/definitions/ChromecastSourceConfig" } ], "definitions": { - "Record": { - "title": "Record", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData": { + "ChromecastData": { "properties": { "allowUnknownMedia": { "anyOf": [ @@ -115,7 +111,7 @@ "devices": { "description": "A list of Google Cast devices to monitor\n\nIf this is used then `useAutoDiscovery` is set to FALSE, if not explicitly set", "items": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo" + "$ref": "#/definitions/ChromecastDeviceInfo" }, "title": "devices", "type": "array" @@ -160,7 +156,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -218,10 +214,10 @@ "title": "whitelistDevices" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", + "title": "ChromecastData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo": { + "ChromecastDeviceInfo": { "properties": { "address": { "description": "The IP address of the device", @@ -244,10 +240,10 @@ "address", "name" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastDeviceInfo", + "title": "ChromecastDeviceInfo", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceConfig": { + "ChromecastSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -264,7 +260,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastData", + "$ref": "#/definitions/ChromecastData", "title": "data" }, "enable": { @@ -282,17 +278,64 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/chromecast\",{assert:{\"resolution-mode\":\"import\"}}).ChromecastSourceConfig", + "title": "ChromecastSourceConfig", + "type": "object" + }, + "CommonSourceOptions": { + "properties": { + "logFilterFailure": { + "default": "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.", + "enum": [ + "debug", + false, + "warn" + ], + "examples": [ + "warn" + ], + "title": "logFilterFailure" + }, + "logPayload": { + "default": false, + "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", + "examples": [ + false + ], + "title": "logPayload", + "type": "boolean" + }, + "logPlayerState": { + "default": false, + "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", + "examples": [ + false + ], + "title": "logPlayerState", + "type": "boolean" + }, + "scrobbleBacklog": { + "default": true, + "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", + "examples": [ + true, + false + ], + "title": "scrobbleBacklog", + "type": "boolean" + } + }, + "title": "CommonSourceOptions", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData": { + "DeezerData": { "properties": { "clientId": { "description": "deezer client id", @@ -360,7 +403,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -370,10 +413,10 @@ "clientSecret", "redirectUri" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", + "title": "DeezerData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceConfig": { + "DeezerSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -390,7 +433,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerData", + "$ref": "#/definitions/DeezerData", "title": "data" }, "enable": { @@ -408,88 +451,144 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/deezer\",{assert:{\"resolution-mode\":\"import\"}}).DeezerSourceConfig", + "title": "DeezerSourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions": { + "JRiverData": { "properties": { - "logFilterFailure": { - "default": "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.", - "enum": [ - "debug", - false, - "warn" + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", + "examples": [ + 10 ], + "title": "interval", + "type": "number" + }, + "maxInterval": { + "default": 30, + "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)", "examples": [ - "warn" + 30 ], - "title": "logFilterFailure" + "title": "maxInterval", + "type": "number" }, - "logPayload": { - "default": false, - "description": "If this source has INGRESS to MS (sends a payload, rather than MS GETTING requesting a payload)\nthen setting this option to true will make MS log the payload JSON to DEBUG output", + "maxPollRetries": { + "default": 5, + "description": "default # of automatic polling restarts on error", "examples": [ - false + 5 ], - "title": "logPayload", - "type": "boolean" + "title": "maxPollRetries", + "type": "number" }, - "logPlayerState": { - "default": false, - "description": "For Sources that track Player State (currently playing) this logs a simple player state/summary to DEBUG output", + "maxRequestRetries": { + "default": 1, + "description": "default # of http request retries a source can make before error is thrown", "examples": [ - false + 1 ], - "title": "logPlayerState", - "type": "boolean" + "title": "maxRequestRetries", + "type": "number" }, - "scrobbleBacklog": { - "default": true, - "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", + "options": { + "$ref": "#/definitions/Record", + "title": "options" + }, + "password": { + "description": "If you have enabled authentication, the password you set", + "title": "password", + "type": "string" + }, + "retryMultiplier": { + "default": 1.5, + "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", "examples": [ - true, - false + 1.5 ], - "title": "scrobbleBacklog", - "type": "boolean" + "title": "retryMultiplier", + "type": "number" + }, + "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.", + "title": "scrobbleThresholds" + }, + "url": { + "default": "http://localhost:52199/MCWS/v1/", + "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/" + ], + "title": "url", + "type": "string" + }, + "username": { + "description": "If you have enabled authentication, the username you set", + "title": "username", + "type": "string" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "required": [ + "url" + ], + "title": "JRiverData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds": { + "JRiverSourceConfig": { "properties": { - "duration": { - "default": 240, - "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", + "clients": { + "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", "examples": [ - 240 + [ + "MyMalojaConfigName", + "MyLastFMConfigName" + ] ], - "title": "duration", - "type": "number" + "items": { + "type": "string" + }, + "title": "clients", + "type": "array" }, - "percent": { - "default": 50, - "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.", + "data": { + "$ref": "#/definitions/JRiverData", + "title": "data" + }, + "enable": { + "default": true, + "description": "Should MS use this client/source? Defaults to true", "examples": [ - 50 + true ], - "title": "percent", - "type": "number" + "title": "enable", + "type": "boolean" + }, + "name": { + "description": "Unique identifier for this source.", + "title": "name", + "type": "string" + }, + "options": { + "$ref": "#/definitions/CommonSourceOptions", + "title": "options" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).ScrobbleThresholds", + "required": [ + "data" + ], + "title": "JRiverSourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData": { + "JellyData": { "properties": { "maxPollRetries": { "default": 5, @@ -548,7 +647,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -594,137 +693,10 @@ "title": "users" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceConfig": { - "properties": { - "clients": { - "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", - "examples": [ - [ - "MyMalojaConfigName", - "MyLastFMConfigName" - ] - ], - "items": { - "type": "string" - }, - "title": "clients", - "type": "array" - }, - "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellyData", - "title": "data" - }, - "enable": { - "default": true, - "description": "Should MS use this client/source? Defaults to true", - "examples": [ - true - ], - "title": "enable", - "type": "boolean" - }, - "name": { - "description": "Unique identifier for this source.", - "title": "name", - "type": "string" - }, - "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", - "title": "options" - } - }, - "required": [ - "data" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jellyfin\",{assert:{\"resolution-mode\":\"import\"}}).JellySourceConfig", - "type": "object" - }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData": { - "properties": { - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 - ], - "title": "interval", - "type": "number" - }, - "maxInterval": { - "default": 30, - "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)", - "examples": [ - 30 - ], - "title": "maxInterval", - "type": "number" - }, - "maxPollRetries": { - "default": 5, - "description": "default # of automatic polling restarts on error", - "examples": [ - 5 - ], - "title": "maxPollRetries", - "type": "number" - }, - "maxRequestRetries": { - "default": 1, - "description": "default # of http request retries a source can make before error is thrown", - "examples": [ - 1 - ], - "title": "maxRequestRetries", - "type": "number" - }, - "options": { - "$ref": "#/definitions/Record", - "title": "options" - }, - "password": { - "description": "If you have enabled authentication, the password you set", - "title": "password", - "type": "string" - }, - "retryMultiplier": { - "default": 1.5, - "description": "default retry delay multiplier (retry attempt * multiplier = # of seconds to wait before retrying)", - "examples": [ - 1.5 - ], - "title": "retryMultiplier", - "type": "number" - }, - "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", - "title": "scrobbleThresholds" - }, - "url": { - "default": "http://localhost:52199/MCWS/v1/", - "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/" - ], - "title": "url", - "type": "string" - }, - "username": { - "description": "If you have enabled authentication, the username you set", - "title": "username", - "type": "string" - } - }, - "required": [ - "url" - ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", + "title": "JellyData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceConfig": { + "JellySourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -741,7 +713,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverData", + "$ref": "#/definitions/JellyData", "title": "data" }, "enable": { @@ -759,17 +731,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/jriver\",{assert:{\"resolution-mode\":\"import\"}}).JRiverSourceConfig", + "title": "JellySourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData": { + "KodiData": { "properties": { "interval": { "default": 10, @@ -826,7 +798,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -850,10 +822,10 @@ "url", "username" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", + "title": "KodiData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceConfig": { + "KodiSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -870,7 +842,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiData", + "$ref": "#/definitions/KodiData", "title": "data" }, "enable": { @@ -888,17 +860,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/kodi\",{assert:{\"resolution-mode\":\"import\"}}).KodiSourceConfig", + "title": "KodiSourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData": { + "LastFmSourceData": { "properties": { "apiKey": { "description": "API Key generated from Last.fm account", @@ -967,7 +939,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -989,10 +961,10 @@ "apiKey", "secret" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", + "title": "LastFmSourceData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmSourceConfig": { + "LastfmSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1021,7 +993,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastFmSourceData", + "$ref": "#/definitions/LastFmSourceData", "title": "data" }, "enable": { @@ -1039,17 +1011,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/lastfm\",{assert:{\"resolution-mode\":\"import\"}}).LastfmSourceConfig", + "title": "LastfmSourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceConfig": { + "ListenBrainzSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1078,7 +1050,7 @@ "type": "string" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", + "$ref": "#/definitions/ListenBrainzSourceData", "title": "data" }, "enable": { @@ -1096,17 +1068,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceConfig", + "title": "ListenBrainzSourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData": { + "ListenBrainzSourceData": { "properties": { "interval": { "default": 10, @@ -1158,7 +1130,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1189,45 +1161,31 @@ "token", "username" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/listenbrainz\",{assert:{\"resolution-mode\":\"import\"}}).ListenBrainzSourceData", + "title": "ListenBrainzSourceData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData": { + "MPRISData": { "properties": { - "albumBlacklist": { - "default": [ - "Soundcloud" + "blacklist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "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.", + "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", "examples": [ [ - "Soundcloud", - "Mixcloud" + "spotify", + "vlc" ] ], - "items": { - "type": "string" - }, - "title": "albumBlacklist", - "type": "array" - }, - "interval": { - "default": 10, - "description": "How long to wait before polling the source API for new tracks (in seconds)", - "examples": [ - 10 - ], - "title": "interval", - "type": "number" - }, - "maxInterval": { - "default": 30, - "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)", - "examples": [ - 30 - ], - "title": "maxInterval", - "type": "number" + "title": "blacklist" }, "maxPollRetries": { "default": 5, @@ -1261,40 +1219,36 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "uriBlacklist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriBlacklist", - "type": "array" - }, - "uriWhitelist": { - "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.", - "items": { - "type": "string" - }, - "title": "uriWhitelist", - "type": "array" - }, - "url": { - "default": "ws://localhost:6680/mopidy/ws/", - "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/`", + "whitelist": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", "examples": [ - "ws://localhost:6680/mopidy/ws/" + [ + "spotify", + "vlc" + ] ], - "title": "url", - "type": "string" + "title": "whitelist" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", + "title": "MPRISData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceConfig": { + "MPRISSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1311,7 +1265,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidyData", + "$ref": "#/definitions/MPRISData", "title": "data" }, "enable": { @@ -1329,38 +1283,52 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mopidy\",{assert:{\"resolution-mode\":\"import\"}}).MopidySourceConfig", + "title": "MPRISSourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData": { + "MopidyData": { "properties": { - "blacklist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } + "albumBlacklist": { + "default": [ + "Soundcloud" ], - "description": "DO NOT scrobble from any players that START WITH these values, case-insensitive", + "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": [ [ - "spotify", - "vlc" + "Soundcloud", + "Mixcloud" ] ], - "title": "blacklist" + "items": { + "type": "string" + }, + "title": "albumBlacklist", + "type": "array" + }, + "interval": { + "default": 10, + "description": "How long to wait before polling the source API for new tracks (in seconds)", + "examples": [ + 10 + ], + "title": "interval", + "type": "number" + }, + "maxInterval": { + "default": 30, + "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)", + "examples": [ + 30 + ], + "title": "maxInterval", + "type": "number" }, "maxPollRetries": { "default": 5, @@ -1394,36 +1362,40 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, - "whitelist": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "string" - } - ], - "description": "ONLY from any players that START WITH these values, case-insensitive\n\nIf whitelist is present then blacklist is ignored", + "uriBlacklist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriBlacklist", + "type": "array" + }, + "uriWhitelist": { + "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.", + "items": { + "type": "string" + }, + "title": "uriWhitelist", + "type": "array" + }, + "url": { + "default": "ws://localhost:6680/mopidy/ws/", + "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": [ - [ - "spotify", - "vlc" - ] + "ws://localhost:6680/mopidy/ws/" ], - "title": "whitelist" + "title": "url", + "type": "string" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", + "title": "MopidyData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceConfig": { + "MopidySourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1440,7 +1412,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISData", + "$ref": "#/definitions/MopidyData", "title": "data" }, "enable": { @@ -1458,17 +1430,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/mpris\",{assert:{\"resolution-mode\":\"import\"}}).MPRISSourceConfig", + "title": "MopidySourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceConfig": { + "PlexSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1485,7 +1457,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", + "$ref": "#/definitions/PlexSourceData", "title": "data" }, "enable": { @@ -1503,17 +1475,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceConfig", + "title": "PlexSourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData": { + "PlexSourceData": { "properties": { "libraries": { "anyOf": [ @@ -1584,7 +1556,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1630,10 +1602,38 @@ "title": "user" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", + "title": "PlexSourceData", + "type": "object" + }, + "Record": { + "title": "Record", + "type": "object" + }, + "ScrobbleThresholds": { + "properties": { + "duration": { + "default": 240, + "description": "The number of seconds a track has been listened to before it should be considered scrobbled.\n\nSet to null to disable.", + "examples": [ + 240 + ], + "title": "duration", + "type": "number" + }, + "percent": { + "default": 50, + "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.", + "examples": [ + 50 + ], + "title": "percent", + "type": "number" + } + }, + "title": "ScrobbleThresholds", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceConfig": { + "SpotifySourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1650,7 +1650,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", + "$ref": "#/definitions/SpotifySourceData", "title": "data" }, "enable": { @@ -1668,17 +1668,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceConfig", + "title": "SpotifySourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData": { + "SpotifySourceData": { "properties": { "clientId": { "description": "spotify client id", @@ -1755,7 +1755,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -1765,10 +1765,10 @@ "clientSecret", "redirectUri" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/spotify\",{assert:{\"resolution-mode\":\"import\"}}).SpotifySourceData", + "title": "SpotifySourceData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubSonicSourceConfig": { + "SubSonicSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1785,7 +1785,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", + "$ref": "#/definitions/SubsonicData", "title": "data" }, "enable": { @@ -1803,17 +1803,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubSonicSourceConfig", + "title": "SubSonicSourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData": { + "SubsonicData": { "properties": { "ignoreTlsErrors": { "default": false, @@ -1885,7 +1885,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -1908,10 +1908,10 @@ "url", "user" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/subsonic\",{assert:{\"resolution-mode\":\"import\"}}).SubsonicData", + "title": "SubsonicData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceConfig": { + "TautulliSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -1928,7 +1928,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/plex\",{assert:{\"resolution-mode\":\"import\"}}).PlexSourceData", + "$ref": "#/definitions/PlexSourceData", "title": "data" }, "enable": { @@ -1946,17 +1946,17 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/tautulli\",{assert:{\"resolution-mode\":\"import\"}}).TautulliSourceConfig", + "title": "TautulliSourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData": { + "WebScrobblerData": { "properties": { "blacklist": { "anyOf": [ @@ -2035,7 +2035,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" }, @@ -2067,10 +2067,10 @@ "title": "whitelist" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", + "title": "WebScrobblerData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceConfig": { + "WebScrobblerSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2087,7 +2087,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerData", + "$ref": "#/definitions/WebScrobblerData", "title": "data" }, "enable": { @@ -2105,14 +2105,14 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/webscrobbler\",{assert:{\"resolution-mode\":\"import\"}}).WebScrobblerSourceConfig", + "title": "WebScrobblerSourceConfig", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData": { + "YTMusicData": { "properties": { "authUser": { "description": "If the 'X-Goog-AuthUser' header is present in the Request Headers for music.youtube.com it must also be included", @@ -2177,7 +2177,7 @@ "type": "number" }, "scrobbleThresholds": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).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.", "title": "scrobbleThresholds" } @@ -2185,10 +2185,10 @@ "required": [ "cookie" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", + "title": "YTMusicData", "type": "object" }, - "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceConfig": { + "YTMusicSourceConfig": { "properties": { "clients": { "description": "Restrict scrobbling tracks played from this source to Clients with names from this list. If list is empty is not present Source scrobbles to all configured Clients.", @@ -2205,7 +2205,7 @@ "type": "array" }, "data": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicData", + "$ref": "#/definitions/YTMusicData", "title": "data" }, "enable": { @@ -2223,14 +2223,14 @@ "type": "string" }, "options": { - "$ref": "#/definitions/import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/index\",{assert:{\"resolution-mode\":\"import\"}}).CommonSourceOptions", + "$ref": "#/definitions/CommonSourceOptions", "title": "options" } }, "required": [ "data" ], - "title": "import(\"/home/foxx/code/multi-scrobbler/src/backend/common/infrastructure/config/source/ytmusic\",{assert:{\"resolution-mode\":\"import\"}}).YTMusicSourceConfig", + "title": "YTMusicSourceConfig", "type": "object" } } -- 2.51.2 From 1e1b5f56ceb20285ba1ac07dcb60d26711644c9e Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 14 Feb 2024 16:32:38 -0500 Subject: [PATCH 09/34] refactor: Replace dbus-next with dbus-ts #142 --- package-lock.json | 903 ++--------------------------- package.json | 6 +- src/backend/sources/MPRISSource.ts | 62 +- 3 files changed, 78 insertions(+), 893 deletions(-) diff --git a/package-lock.json b/package-lock.json index c41d4db4..b83e7ec9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "compare-versions": "^4.1.2", "concat-stream": "^2.0.0", "dayjs": "^1.10.4", - "dbus-next": "0.10.2", + "dbus-ts": "^0.0.7", "dotenv": "^10.0.0", "es6-error": "^4.1.1", "express": "^4.17.1", @@ -78,6 +78,8 @@ "youtube-music-ts-api": "^1.7.0" }, "devDependencies": { + "@dbus-types/dbus": "^0.0.4", + "@dbus-types/notifications": "^0.0.5", "@faker-js/faker": "^8.1.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@testing-library/jest-dom": "^5.17.0", @@ -1039,6 +1041,20 @@ "kuler": "^2.0.0" } }, + "node_modules/@dbus-types/dbus": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@dbus-types/dbus/-/dbus-0.0.4.tgz", + "integrity": "sha512-KRD48WayYXEmFfVyR4zrCpZ5tviBSU2NXddMOQ4AW1zPfLtEkg7gvVvZUL1/DlThUoFwMC+RRR+EK0yGtZq3Jw==" + }, + "node_modules/@dbus-types/notifications": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@dbus-types/notifications/-/notifications-0.0.5.tgz", + "integrity": "sha512-wDGzHeRcqmga2tGrCw+tNj33Q69HUguILygkrje2QeBlBiwZJbBmCuz3RHamMbATkulVtewvUr79QWRZlNHUww==", + "dev": true, + "dependencies": { + "@dbus-types/dbus": "^0.0.4" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", @@ -1489,6 +1505,19 @@ "node": ">= 6.4.0" } }, + "node_modules/@homebridge/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@homebridge/long/-/long-5.2.1.tgz", + "integrity": "sha512-i5Df8R63XNPCn+Nj1OgAoRdw9e+jHUQb3CNUbvJneI2iu3j4+OtzQj+5PA1Ce+747NR1SPqZSvyvD483dOT3AA==" + }, + "node_modules/@homebridge/put": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@homebridge/put/-/put-0.0.8.tgz", + "integrity": "sha512-mwxLHHqKebOmOSU0tsPEWQSBHGApPhuaqtNpCe7U+AMdsduweANiu64E9SXXUtdpyTjsOpgSMLhD1+kbLHD2gA==", + "engines": { + "node": ">=0.3.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1798,14 +1827,6 @@ "node": ">= 8" } }, - "node_modules/@nornagon/put": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@nornagon/put/-/put-0.0.8.tgz", - "integrity": "sha512-ugvXJjwF5ldtUpa7D95kruNJ41yFQDEKyF5CW4TgKJnh+W/zmlBzXXeKTyqIgwMFrkePN2JqOBqcF0M0oOunow==", - "engines": { - "node": ">=0.3.0" - } - }, "node_modules/@open-draft/deferred-promise": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", @@ -2707,7 +2728,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "devOptional": true + "dev": true }, "node_modules/accepts": { "version": "1.3.8", @@ -2871,64 +2892,12 @@ "node": ">=8" } }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "optional": true - }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, - "node_modules/are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "node_modules/are-we-there-yet/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "optional": true - }, - "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "optional": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/are-we-there-yet/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - }, - "node_modules/are-we-there-yet/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -2972,15 +2941,6 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "optional": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, "node_modules/assert": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", @@ -2994,15 +2954,6 @@ "util": "^0.12.5" } }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "optional": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -3107,21 +3058,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "optional": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "optional": true - }, "node_modules/axios": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.1.tgz", @@ -3174,15 +3110,6 @@ "node": ">=6.0.0" } }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "optional": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, "node_modules/better-sse": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/better-sse/-/better-sse-0.8.0.tgz", @@ -3200,15 +3127,6 @@ "node": ">=8" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -3469,12 +3387,6 @@ } ] }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "optional": true - }, "node_modules/castv2": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/castv2/-/castv2-0.1.10.tgz", @@ -3581,15 +3493,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "optional": true, - "engines": { - "node": ">=10" - } - }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -3724,15 +3627,6 @@ "node": ">=6" } }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -3859,12 +3753,6 @@ "typedarray": "^0.0.6" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "optional": true - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -3908,12 +3796,6 @@ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "optional": true - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -3971,41 +3853,23 @@ "node": ">=0.4.0" } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "optional": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" }, - "node_modules/dbus-next": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/dbus-next/-/dbus-next-0.10.2.tgz", - "integrity": "sha512-kLNQoadPstLgKKGIXKrnRsMgtAK/o+ix3ZmcfTfvBHzghiO9yHXpoKImGnB50EXwnfSFaSAullW/7UrSkAISSQ==", + "node_modules/dbus-ts": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/dbus-ts/-/dbus-ts-0.0.7.tgz", + "integrity": "sha512-2Iig5znVcrIMSAgTxy5DcUm2mIkgbFGP01YUSxSakkzQzrx+OH24LMIiopDAEWS8LR6V3/eVywO72us438eX0Q==", "dependencies": { - "@nornagon/put": "0.0.8", - "event-stream": "3.3.4", - "hexy": "^0.2.10", - "jsbi": "^2.0.5", - "long": "^4.0.0", - "safe-buffer": "^5.1.1", - "xml2js": "^0.4.17" - }, - "optionalDependencies": { - "usocket": "^0.3.0" + "@dbus-types/dbus": "^0.0.4", + "@homebridge/long": "^5.2.1", + "@homebridge/put": "^0.0.8", + "xml2js": "^0.4.23" } }, - "node_modules/dbus-next/node_modules/xml2js": { + "node_modules/dbus-ts/node_modules/xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", @@ -4190,12 +4054,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "optional": true - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4264,11 +4122,6 @@ "node": ">=10" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, "node_modules/duplexer3": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", @@ -4279,16 +4132,6 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "optional": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4325,15 +4168,6 @@ "once": "^1.4.0" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "optional": true, - "engines": { - "node": ">=6" - } - }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -4455,20 +4289,6 @@ "node": ">= 0.6" } }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -4604,12 +4424,6 @@ "node": ">= 0.8" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "optional": true - }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -4624,15 +4438,6 @@ "node": ">=4" } }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "engines": [ - "node >=0.6.0" - ], - "optional": true - }, "node_modules/eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", @@ -4661,12 +4466,6 @@ "node": ">=8.6.0" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "optional": true - }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -4717,12 +4516,6 @@ "moment": "^2.29.1" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4874,15 +4667,6 @@ "node": ">=8.0.0" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "optional": true, - "engines": { - "node": "*" - } - }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -4938,11 +4722,6 @@ "node": ">= 0.6" } }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==" - }, "node_modules/fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", @@ -4977,36 +4756,6 @@ "node": ">=10" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5042,69 +4791,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", - "optional": true, - "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "node_modules/gauge/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gauge/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "optional": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gauge/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "optional": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gauge/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "optional": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5180,15 +4866,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "optional": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -5315,51 +4992,6 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "optional": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/har-validator/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/har-validator/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "optional": true - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -5425,12 +5057,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "optional": true - }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -5490,14 +5116,6 @@ "node": ">=8" } }, - "node_modules/hexy": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/hexy/-/hexy-0.2.11.tgz", - "integrity": "sha512-ciq6hFsSG/Bpt2DmrZJtv+56zpPdnq+NQ4ijEFrveKN0ZG1mhl/LdT1NQZ9se6ty1fACcI4d4vYqC9v8EYpH2A==", - "bin": { - "hexy": "bin/hexy_cmd.js" - } - }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -5537,21 +5155,6 @@ "node": ">= 0.8" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "optional": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6032,7 +5635,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "devOptional": true + "dev": true }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -6115,12 +5718,6 @@ "ws": "*" } }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "optional": true - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -6386,17 +5983,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbi": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-2.0.5.tgz", - "integrity": "sha512-TzO/62Hxeb26QMb4IGlI/5X+QLr9Uqp1FPkwp2+KOICW+Q+vSuFj61c8pkT6wAns4WcK56X7CmSHhJeDGWOqxQ==" - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "optional": true - }, "node_modules/jscodeshift": { "version": "0.15.1", "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.15.1.tgz", @@ -6481,12 +6067,6 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "optional": true - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -6581,21 +6161,6 @@ "node": "*" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "optional": true, - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6822,11 +6387,6 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "devOptional": true }, - "node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==" - }, "node_modules/marked": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", @@ -6967,54 +6527,11 @@ "node": ">=8" } }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "optional": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, "node_modules/mitt": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.2.0.tgz", "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==" }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mocha": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", @@ -7250,12 +6767,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", - "optional": true - }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -7321,78 +6832,6 @@ "node": "*" } }, - "node_modules/node-gyp": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", - "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==", - "optional": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.3", - "nopt": "^5.0.0", - "npmlog": "^4.1.2", - "request": "^2.88.2", - "rimraf": "^3.0.2", - "semver": "^7.3.2", - "tar": "^6.0.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/node-gyp/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "optional": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "optional": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -7556,18 +6995,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "optional": true, - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, "node_modules/ntfy": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/ntfy/-/ntfy-1.0.5.tgz", @@ -7579,15 +7006,6 @@ "node": ">= 10.9" } }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -7788,15 +7206,6 @@ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "optional": true, - "engines": { - "node": "*" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8280,20 +7689,6 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", - "dependencies": { - "through": "~2.3" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "optional": true - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -8572,12 +7967,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "optional": true - }, "node_modules/process-on-spawn": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", @@ -8647,12 +8036,6 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "optional": true - }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -8977,71 +8360,6 @@ "node": ">=4" } }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "optional": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "optional": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/request/node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "optional": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "optional": true, - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9140,7 +8458,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "devOptional": true, + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -9380,7 +8698,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "devOptional": true + "dev": true }, "node_modules/set-function-length": { "version": "1.2.0", @@ -9488,7 +8806,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true + "dev": true }, "node_modules/simple-swizzle": { "version": "0.2.2", @@ -9610,17 +8928,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, "node_modules/spotify-web-api-node": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/spotify-web-api-node/-/spotify-web-api-node-5.0.2.tgz", @@ -9635,31 +8942,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "optional": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -9688,14 +8970,6 @@ "node": ">= 0.4" } }, - "node_modules/stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", - "dependencies": { - "duplexer": "~0.1.1" - } - }, "node_modules/stream-split": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stream-split/-/stream-split-1.1.0.tgz", @@ -10024,29 +9298,6 @@ "node": ">=10.13.0" } }, - "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", - "optional": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, "node_modules/temp": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", @@ -10200,19 +9451,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "optional": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -10342,24 +9580,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "optional": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "optional": true - }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -10648,18 +9868,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/usocket": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/usocket/-/usocket-0.3.0.tgz", - "integrity": "sha512-V/H02RNiaOCJZuPoKont/y12VJaImC6C5xW7OzPFjYu9qnig0yv9hyp9E7Wqjm6d8yZuZouH3NAfDATVMgh2SQ==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.14.2", - "node-gyp": "^7.1.2" - } - }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -10716,20 +9924,6 @@ "node": ">= 0.8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "engines": [ - "node >=0.6.0" - ], - "optional": true, - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/vite": { "version": "5.0.12", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", @@ -10903,15 +10097,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "optional": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/winston": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", diff --git a/package.json b/package.json index 2a72f316..ace94138 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "compare-versions": "^4.1.2", "concat-stream": "^2.0.0", "dayjs": "^1.10.4", - "dbus-next": "0.10.2", + "dbus-ts": "^0.0.7", "dotenv": "^10.0.0", "es6-error": "^4.1.1", "express": "^4.17.1", @@ -85,10 +85,10 @@ "normalize-url": "^6.1.0", "ntfy": "1.0.5", "object-hash": "^3.0.0", - "patch-package": "^8.0.0", "p-event": "^4.2.0", "passport": "^0.6.0", "passport-deezer": "^0.2.0", + "patch-package": "^8.0.0", "pony-cause": "^1.1.1", "postcss": "^8.4.33", "react": "^18.2.0", @@ -112,6 +112,8 @@ "youtube-music-ts-api": "^1.7.0" }, "devDependencies": { + "@dbus-types/dbus": "^0.0.4", + "@dbus-types/notifications": "^0.0.5", "@faker-js/faker": "^8.1.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@testing-library/jest-dom": "^5.17.0", diff --git a/src/backend/sources/MPRISSource.ts b/src/backend/sources/MPRISSource.ts index b47f5fde..13c6c137 100644 --- a/src/backend/sources/MPRISSource.ts +++ b/src/backend/sources/MPRISSource.ts @@ -1,4 +1,3 @@ -import dbus, {ClientInterface, Variant} from 'dbus-next'; import dayjs from "dayjs"; import { MPRIS_IFACE, @@ -17,6 +16,8 @@ import { removeDuplicates } from "../utils.js"; import EventEmitter from "events"; import {ErrorWithCause} from "pony-cause"; import { PlayObject } from "../../core/Atomic.js"; +import {DBusInterface, sessionBus} from 'dbus-ts'; +import { Interfaces as Notifications } from '@dbus-types/notifications' export class MPRISSource extends MemorySource { @@ -101,32 +102,29 @@ export class MPRISSource extends MemorySource { } protected getDBus = async () => { - const bus = dbus.sessionBus(); - const obj = await bus.getProxyObject('org.freedesktop.DBus', '/org/freedesktop/DBus'); - return obj.getInterface('org.freedesktop.DBus'); + const busNew = await sessionBus(); + const obj = await busNew.getInterface('org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus'); + return obj; } - protected listAll = async () => { + protected listNew = async () => { let iface = await this.getDBus(); - let names = await iface.ListNames(); - return names.filter((n) => n.startsWith('org.mpris.MediaPlayer2')) + let names = (await iface.ListNames())[0]; + return names.filter((n) => n.includes('org.mpris.MediaPlayer2')) } getPlayersInfo = async (activeOnly = true): Promise => { - const list = await this.listAll(); - let bus = dbus.sessionBus(); + const busNew = await sessionBus(); const playerInfos: PlayerInfo[] = []; - for (const playerName of list) { + const newList = await this.listNew(); + + for (const playerName of newList) { const plainPlayerName = playerName.replace('org.mpris.MediaPlayer2.', ''); try { - let obj = await bus.getProxyObject(playerName, MPRIS_PATH); - - //let player = obj.getInterface(MPRIS_IFACE); - let props = obj.getInterface(PROPERTIES_IFACE); - + let props = await busNew.getInterface(playerName, MPRIS_PATH, MPRIS_IFACE); // may not always have position available! can fallback to undefined for this let pos: number | undefined; try { @@ -145,35 +143,39 @@ export class MPRISSource extends MemorySource { position: pos, metadata }); - } catch (e) { + } + catch (e) { this.logger.warn(new ErrorWithCause(`Could not parse D-bus info for player ${plainPlayerName}`, {cause: e})); } + } + return playerInfos; } - protected getPlayerPosition = async (props: ClientInterface): Promise => { + protected getPlayerPosition = async (props: DBusInterface): Promise => { try { - const pos = await props.Get(MPRIS_IFACE, 'Position'); - return dayjs.duration({milliseconds: Number(pos.value / 1000n)}).asSeconds(); + const pos = await props['Position']; + // microseconds + return dayjs.duration({milliseconds: Number(pos.value / 1000000)}).asSeconds(); } catch(e) { throw new ErrorWithCause('Could not get player Position', {cause: e}); } } - protected getPlayerStatus = async (props: ClientInterface): Promise => { + protected getPlayerStatus = async (props: DBusInterface): Promise => { try { - const status = await props.Get(MPRIS_IFACE, 'PlaybackStatus'); - return status.value as PlaybackStatus; + const status = await props['PlaybackStatus']; //Get(MPRIS_IFACE, 'PlaybackStatus'); + return status as PlaybackStatus; } catch (e) { throw new ErrorWithCause('Could not get player PlaybackStatus', {cause: e}) } } - protected getPlayerMetadata = async (props: ClientInterface): Promise => { + protected getPlayerMetadata = async (props: DBusInterface): Promise => { try { - const metadata = await props.Get(MPRIS_IFACE, 'Metadata'); - return this.metadataToPlain(metadata.value); + const metadata = await props['Metadata']; //.Get(MPRIS_IFACE, 'Metadata'); + return this.metadataToPlain(metadata); } catch(e) { throw new ErrorWithCause('Could not get player Metadata', {cause: e}); } @@ -188,13 +190,9 @@ export class MPRISSource extends MemorySource { continue; } const plainKey = k.replace(/mpris:|xesam:/, ''); - if (value instanceof Variant) { - if (typeof value.value === 'bigint') { - // in this context we're using it as a duration (track length or playback position) - metadataPlain[plainKey] = dayjs.duration({milliseconds: Number(value.value / 1000n)}).asSeconds(); - } else { - metadataPlain[plainKey] = value.value; - } + if(plainKey === 'length' && typeof value === 'number') { + // microseconds + metadataPlain[plainKey] = value / 1000000 } else { metadataPlain[plainKey] = value; } -- 2.51.2 From 5ab8f00aeb7faff2d2066382076f51e017e608d4 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 15 Feb 2024 09:23:26 -0500 Subject: [PATCH 10/34] Fix microsecond conversion --- src/backend/sources/MPRISSource.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/backend/sources/MPRISSource.ts b/src/backend/sources/MPRISSource.ts index 13c6c137..cd24d2f9 100644 --- a/src/backend/sources/MPRISSource.ts +++ b/src/backend/sources/MPRISSource.ts @@ -7,7 +7,6 @@ import { PLAYBACK_STATUS_STOPPED, PlaybackStatus, PlayerInfo, - PROPERTIES_IFACE, } from "../common/infrastructure/config/source/mpris.js"; import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; import MemorySource from "./MemorySource.js"; @@ -157,7 +156,7 @@ export class MPRISSource extends MemorySource { try { const pos = await props['Position']; // microseconds - return dayjs.duration({milliseconds: Number(pos.value / 1000000)}).asSeconds(); + return dayjs.duration({milliseconds: Number(pos / 1000)}).asSeconds(); } catch(e) { throw new ErrorWithCause('Could not get player Position', {cause: e}); } @@ -165,7 +164,7 @@ export class MPRISSource extends MemorySource { protected getPlayerStatus = async (props: DBusInterface): Promise => { try { - const status = await props['PlaybackStatus']; //Get(MPRIS_IFACE, 'PlaybackStatus'); + const status = await props['PlaybackStatus']; return status as PlaybackStatus; } catch (e) { throw new ErrorWithCause('Could not get player PlaybackStatus', {cause: e}) @@ -174,7 +173,7 @@ export class MPRISSource extends MemorySource { protected getPlayerMetadata = async (props: DBusInterface): Promise => { try { - const metadata = await props['Metadata']; //.Get(MPRIS_IFACE, 'Metadata'); + const metadata = await props['Metadata']; return this.metadataToPlain(metadata); } catch(e) { throw new ErrorWithCause('Could not get player Metadata', {cause: e}); @@ -191,7 +190,7 @@ export class MPRISSource extends MemorySource { } const plainKey = k.replace(/mpris:|xesam:/, ''); if(plainKey === 'length' && typeof value === 'number') { - // microseconds + // microseconds to seconds metadataPlain[plainKey] = value / 1000000 } else { metadataPlain[plainKey] = value; -- 2.51.2 From 4aa875660f8fa7ee5ef67acef2fc7a6b0a4515ef Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 15 Feb 2024 09:25:32 -0500 Subject: [PATCH 11/34] chore: Remove unused es6-error package --- package-lock.json | 4 ++-- package.json | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b83e7ec9..fa8acc2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "dayjs": "^1.10.4", "dbus-ts": "^0.0.7", "dotenv": "^10.0.0", - "es6-error": "^4.1.1", "express": "^4.17.1", "express-session": "^1.17.2", "fixed-size-list": "^0.3.0", @@ -4191,7 +4190,8 @@ "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true }, "node_modules/es6-promise": { "version": "4.2.8", diff --git a/package.json b/package.json index ace94138..3f03cdd9 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,6 @@ "dayjs": "^1.10.4", "dbus-ts": "^0.0.7", "dotenv": "^10.0.0", - "es6-error": "^4.1.1", "express": "^4.17.1", "express-session": "^1.17.2", "fixed-size-list": "^0.3.0", -- 2.51.2 From d2d5f4a705d67eb5cc0eb737071f500755102ebf Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 15 Feb 2024 10:10:01 -0500 Subject: [PATCH 12/34] chore: Remove more unused packages --- package-lock.json | 154 +++++++++++++++++++++++++--------------------- package.json | 5 +- 2 files changed, 84 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa8acc2a..638ae613 100644 --- a/package-lock.json +++ b/package-lock.json @@ -114,14 +114,11 @@ "mocha": "10.1.0", "msw": "^2.1.2", "nodemon": "^3.0.3", - "nyc": "^15.1.0", "ts-essentials": "^9.1.2", "typedoc": "^0.25", "typescript": "^5.3.3", "typescript-json-schema": "~0.55", - "vite": "^5.0.11", - "vite-tsconfig-paths": "^4.3.1", - "wtfnode": "^0.9.1" + "vite": "^5.0.11" }, "engines": { "node": ">=18.0.0", @@ -1611,6 +1608,7 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, + "peer": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -1627,6 +1625,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -1636,6 +1635,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -1649,6 +1649,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, + "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -1662,6 +1663,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -1674,6 +1676,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -1689,6 +1692,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -2775,6 +2779,7 @@ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, + "peer": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -2884,6 +2889,7 @@ "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, + "peer": true, "dependencies": { "default-require-extensions": "^3.0.0" }, @@ -2895,7 +2901,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/arg": { "version": "5.0.2", @@ -3300,6 +3307,7 @@ "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, + "peer": true, "dependencies": { "hasha": "^5.0.0", "make-dir": "^3.0.0", @@ -3315,6 +3323,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, + "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -3330,6 +3339,7 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, + "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -3355,6 +3365,7 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -3511,6 +3522,7 @@ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -3901,6 +3913,7 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3965,6 +3978,7 @@ "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", "dev": true, + "peer": true, "dependencies": { "strip-bom": "^4.0.0" }, @@ -4191,7 +4205,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/es6-promise": { "version": "4.2.8", @@ -4659,6 +4674,7 @@ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, + "peer": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^3.0.2" @@ -4740,7 +4756,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "peer": true }, "node_modules/fs-extra": { "version": "9.1.0", @@ -4837,6 +4854,7 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, + "peer": true, "engines": { "node": ">=8.0.0" } @@ -4925,12 +4943,6 @@ "node": ">=4" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -5062,6 +5074,7 @@ "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", "dev": true, + "peer": true, "dependencies": { "is-stream": "^2.0.0", "type-fest": "^0.8.0" @@ -5078,6 +5091,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -5133,7 +5147,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/http-cache-semantics": { "version": "4.1.1", @@ -5635,7 +5650,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -5676,6 +5692,7 @@ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5723,6 +5740,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -5732,6 +5750,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, + "peer": true, "dependencies": { "append-transform": "^2.0.0" }, @@ -5744,6 +5763,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.7.5", "@istanbuljs/schema": "^0.1.2", @@ -5759,6 +5779,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, + "peer": true, "dependencies": { "archy": "^1.0.0", "cross-spawn": "^7.0.3", @@ -5776,6 +5797,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, + "peer": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -5790,6 +5812,7 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -5802,6 +5825,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "peer": true, "dependencies": { "semver": "^7.5.3" }, @@ -5817,6 +5841,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "peer": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -5831,13 +5856,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "peer": true }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, + "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -5852,6 +5879,7 @@ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, + "peer": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -6255,7 +6283,8 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/lodash.mergewith": { "version": "4.6.2", @@ -6837,6 +6866,7 @@ "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, + "peer": true, "dependencies": { "process-on-spawn": "^1.0.0" }, @@ -7011,6 +7041,7 @@ "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", "dev": true, + "peer": true, "dependencies": { "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", @@ -7052,6 +7083,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, + "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -7062,13 +7094,15 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true + "dev": true, + "peer": true }, "node_modules/nyc/node_modules/find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, + "peer": true, "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -7086,6 +7120,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -7099,6 +7134,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -7111,6 +7147,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, + "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -7126,6 +7163,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -7141,6 +7179,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -7153,6 +7192,7 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, + "peer": true, "dependencies": { "find-up": "^4.0.0" }, @@ -7164,13 +7204,15 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/nyc/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, + "peer": true, "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -7193,6 +7235,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, + "peer": true, "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -7439,6 +7482,7 @@ "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, + "peer": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -7471,6 +7515,7 @@ "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", "dev": true, + "peer": true, "dependencies": { "graceful-fs": "^4.1.15", "hasha": "^5.0.0", @@ -7972,6 +8017,7 @@ "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", "dev": true, + "peer": true, "dependencies": { "fromentries": "^1.2.0" }, @@ -8353,6 +8399,7 @@ "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", "dev": true, + "peer": true, "dependencies": { "es6-error": "^4.0.1" }, @@ -8381,7 +8428,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/reselect": { "version": "4.1.8", @@ -8409,6 +8457,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -8459,6 +8508,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, + "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -8698,7 +8748,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/set-function-length": { "version": "1.2.0", @@ -8901,6 +8952,7 @@ "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, + "peer": true, "dependencies": { "foreground-child": "^2.0.0", "is-windows": "^1.0.2", @@ -8918,6 +8970,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, + "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -8940,7 +8993,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "dev": true, + "peer": true }, "node_modules/stack-trace": { "version": "0.0.10", @@ -9050,6 +9104,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -9327,6 +9382,7 @@ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, + "peer": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -9341,6 +9397,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9351,6 +9408,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9536,26 +9594,6 @@ "node": ">=0.3.1" } }, - "node_modules/tsconfck": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.0.1.tgz", - "integrity": "sha512-7ppiBlF3UEddCLeI1JRx5m2Ryq+xk4JrZuq4EuYXykipebaq1dV0Fhgr1hb7CkmHt32QSgOZlcqVLEtHBG4/mg==", - "dev": true, - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -9622,6 +9660,7 @@ "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, + "peer": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -9987,25 +10026,6 @@ "picocolors": "^1.0.0" } }, - "node_modules/vite-tsconfig-paths": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.1.tgz", - "integrity": "sha512-cfgJwcGOsIxXOLU/nELPny2/LUD/lcf1IbfyeKTv2bsupVbTH/xpFtdQlBmIP1GEK2CjjLxYhFfB+QODFAx5aw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "globrex": "^0.1.2", - "tsconfck": "^3.0.1" - }, - "peerDependencies": { - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", @@ -10076,7 +10096,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/which-typed-array": { "version": "1.1.13", @@ -10305,15 +10326,6 @@ } } }, - "node_modules/wtfnode": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/wtfnode/-/wtfnode-0.9.1.tgz", - "integrity": "sha512-Ip6C2KeQPl/F3aP1EfOnPoQk14Udd9lffpoqWDNH3Xt78svxPbv53ngtmtfI0q2Te3oTq79XKTnRNXVIn/GsPA==", - "dev": true, - "bin": { - "wtfnode": "proxy.js" - } - }, "node_modules/xml2js": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", diff --git a/package.json b/package.json index 3f03cdd9..3d4bf03a 100644 --- a/package.json +++ b/package.json @@ -148,14 +148,11 @@ "mocha": "10.1.0", "msw": "^2.1.2", "nodemon": "^3.0.3", - "nyc": "^15.1.0", "ts-essentials": "^9.1.2", "typedoc": "^0.25", "typescript": "^5.3.3", "typescript-json-schema": "~0.55", - "vite": "^5.0.11", - "vite-tsconfig-paths": "^4.3.1", - "wtfnode": "^0.9.1" + "vite": "^5.0.11" }, "browserslist": { "production": [ -- 2.51.2 From 9a5a808780ba7c3de7d5a054477dea7f47f6d0e0 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 15 Feb 2024 12:05:50 -0500 Subject: [PATCH 13/34] fix: Remove alt single quote when normalizing strings --- src/backend/utils/StringUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/StringUtils.ts b/src/backend/utils/StringUtils.ts index be672f78..e88b005f 100644 --- a/src/backend/utils/StringUtils.ts +++ b/src/backend/utils/StringUtils.ts @@ -7,8 +7,8 @@ import {strategies} from '@foxxmd/string-sameness'; const {levenStrategy, diceStrategy} = strategies; // cant use [^\w\s] because this also catches non-english characters -export const SYMBOLS_WHITESPACE_REGEX = new RegExp(/[`=(){}<>;',.~!@#$%^&*_+|:"?\-\\\[\]\/\s]/g); -export const SYMBOLS_REGEX = new RegExp(/[`=(){}<>;',.~!@#$%^&*_+|:"?\-\\\[\]\/]/g); +export const SYMBOLS_WHITESPACE_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\\[\]\/\s]/g); +export const SYMBOLS_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\\[\]\/]/g); export const MULTI_WHITESPACE_REGEX = new RegExp(/\s{2,}/g); export const uniqueNormalizedStrArr = (arr: string[]): string[] => { -- 2.51.2 From 805798218405b7f6fae99735d3d12fa6d01e992a Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 15 Feb 2024 12:04:32 -0500 Subject: [PATCH 14/34] chore: Upgrade string-sameness * Removes dependency on unmaintained dice coeffecient library * New version is esm-only library --- package-lock.json | 42 ++++++++++++++++++------- package.json | 2 +- src/backend/tests/utils/strings.test.ts | 2 +- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 638ae613..d130dfa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@foxxmd/chromecast-client": "^1.0.4", - "@foxxmd/string-sameness": "^0.2.0", + "@foxxmd/string-sameness": "^0.4.0", "@foxxmd/winston": "3.3.31", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", @@ -1469,12 +1469,12 @@ } }, "node_modules/@foxxmd/string-sameness": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@foxxmd/string-sameness/-/string-sameness-0.2.0.tgz", - "integrity": "sha512-b4at6spxVej3IfZA1fVBX1iGZ4UdcEJSXItv89lV2ZNtJOYvShc39gUAyDrtRbrz9yilEvSggyyKKLmC0BXXxg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@foxxmd/string-sameness/-/string-sameness-0.4.0.tgz", + "integrity": "sha512-6O2A+VpmUYiQMtFVfBQHtrbinN7JqRKwUUgBDpAR7nVrYtjFCJSsj8DC2rFLiY81bW9o7FRU6SIuVOoRfBW2pw==", "dependencies": { - "leven": "^3.0.0", - "string-similarity": "^4.0.4" + "dice-coefficient": "^2.1.1", + "leven": "^3.0.0" }, "engines": { "node": ">=18.0.0", @@ -4093,6 +4093,21 @@ "wrappy": "1" } }, + "node_modules/dice-coefficient": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dice-coefficient/-/dice-coefficient-2.1.1.tgz", + "integrity": "sha512-vPTcHmOQAuGvU6eyBtj7QCBwDJh2I7QpbBU51lbgfv7592KjBl6dm0baRBSh9ekt2X91MNAz7OpJrXCIUtDzlw==", + "dependencies": { + "n-gram": "^2.0.0" + }, + "bin": { + "dice-coefficient": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -6796,6 +6811,15 @@ "thenify-all": "^1.0.0" } }, + "node_modules/n-gram": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/n-gram/-/n-gram-2.0.2.tgz", + "integrity": "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -9043,12 +9067,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-similarity": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", - "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info." - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", diff --git a/package.json b/package.json index 3d4bf03a..8e30fd22 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@foxxmd/chromecast-client": "^1.0.4", - "@foxxmd/string-sameness": "^0.2.0", + "@foxxmd/string-sameness": "^0.4.0", "@foxxmd/winston": "3.3.31", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", diff --git a/src/backend/tests/utils/strings.test.ts b/src/backend/tests/utils/strings.test.ts index 1ba15949..a0693691 100644 --- a/src/backend/tests/utils/strings.test.ts +++ b/src/backend/tests/utils/strings.test.ts @@ -111,7 +111,7 @@ describe('String Comparisons', function () { for(const test of tests) { const result = compareNormalizedStrings(test[0], test[1]); - assert.isAtMost( result.highScore, 53, `Comparing: '${test[0]}' | '${test[1]}'`); + assert.isAtMost( result.highScore, 58, `Comparing: '${test[0]}' | '${test[1]}'`); } }); -- 2.51.2 From beaf0f3bfd56ba26977dae3bfe945bb05952422d Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 15 Feb 2024 12:36:34 -0500 Subject: [PATCH 15/34] chore(!): Upgrade Node to LTS for --import support So that we can use tsx with official --import rather than requiring (old-style) which doesn't work with string-sameness esm for some reason https://github.com/mochajs/mocha/issues/5002 https://github.com/mochajs/mocha-examples/pull/76 --- .mocharc.json | 3 ++- .nvmrc | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.mocharc.json b/.mocharc.json index b111ee1a..1cea1217 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,4 +1,5 @@ { "reporter": "dot", - "require": ["tsx"] + "extension": "ts", + "import": "tsx/esm" } diff --git a/.nvmrc b/.nvmrc index 8ddbc0c6..60495ee0 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18.16.0 +v18.19.1 diff --git a/package.json b/package.json index 8e30fd22..210e37b0 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "typedoc": "typedoc", "circular": "madge --circular --extensions ts src/index.ts", "test": "npm run -s test:backend", - "test:backend": "mocha --extension ts --reporter spec --recursive src/backend/tests/**/*.test.ts", + "test:backend": "mocha --reporter spec --recursive src/backend/tests/**/*.test.ts", "fileEndings": "jscodeshift --transformFrom js --transformTo none --importTypes relative --extensions=ts --parser tsx --transform codeshift/transform.ts src/backend", "dev": "APP_VERSION=$npm_package_version nodemon -w src/backend -x tsx src/backend/index.ts", "start": "APP_VERSION=$npm_package_version NODE_ENV=production tsx src/backend/index.ts", -- 2.51.2 From dae4af070fc70bfa463a7ac92bd6f7efa4450258 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 15 Feb 2024 13:01:22 -0500 Subject: [PATCH 16/34] chore: Bump mocha version --- package-lock.json | 42 ++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index d130dfa1..9ae35294 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,7 +111,7 @@ "chai-as-promised": "^7.1.1", "clone": "^2.1.2", "jscodeshift": "^0.15.0", - "mocha": "10.1.0", + "mocha": "^10.3.0", "msw": "^2.1.2", "nodemon": "^3.0.3", "ts-essentials": "^9.1.2", @@ -6577,9 +6577,9 @@ "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==" }, "node_modules/mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", - "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", + "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", @@ -6589,13 +6589,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -6610,10 +6609,25 @@ }, "engines": { "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mocha/node_modules/ms": { @@ -6622,18 +6636,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/mocha/node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", diff --git a/package.json b/package.json index 210e37b0..ad6e4e86 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "chai-as-promised": "^7.1.1", "clone": "^2.1.2", "jscodeshift": "^0.15.0", - "mocha": "10.1.0", + "mocha": "^10.3.0", "msw": "^2.1.2", "nodemon": "^3.0.3", "ts-essentials": "^9.1.2", -- 2.51.2 From 69e15b379728e7bc2aa43caadc262a3a618b8498 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 19 Feb 2024 10:38:59 -0500 Subject: [PATCH 17/34] fix: Refactor static arrow function class fields to normal functions #143 Something about using arrow function form for static class methods causes node debugger to crash. Closes #143 --- src/backend/common/vendor/AbstractApiClient.ts | 2 +- src/backend/common/vendor/LastfmApiClient.ts | 2 +- src/backend/common/vendor/ListenbrainzApiClient.ts | 6 +++--- src/backend/sources/ListenbrainzSource.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/backend/common/vendor/AbstractApiClient.ts b/src/backend/common/vendor/AbstractApiClient.ts index 2d6c564d..fba86e1c 100644 --- a/src/backend/common/vendor/AbstractApiClient.ts +++ b/src/backend/common/vendor/AbstractApiClient.ts @@ -27,7 +27,7 @@ export default abstract class AbstractApiClient { this.options = options; } - static formatPlayObj = (obj: any, options: FormatPlayObjectOptions): PlayObject => { + static formatPlayObj(obj: any, options: FormatPlayObjectOptions): PlayObject { throw new Error('should be overridden'); } } diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index d77183dc..0803fd47 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -47,7 +47,7 @@ export default class LastfmApiClient extends AbstractApiClient { this.client = new LastFm(apiKey as string, secret, session); } - static formatPlayObj = (obj: TrackObject, options: FormatPlayObjectOptions = {}): PlayObject => { + static formatPlayObj(obj: TrackObject, options: FormatPlayObjectOptions = {}): PlayObject { const { artist: { '#text': artists, diff --git a/src/backend/common/vendor/ListenbrainzApiClient.ts b/src/backend/common/vendor/ListenbrainzApiClient.ts index 36cfd766..2888f0bf 100644 --- a/src/backend/common/vendor/ListenbrainzApiClient.ts +++ b/src/backend/common/vendor/ListenbrainzApiClient.ts @@ -291,7 +291,7 @@ export class ListenbrainzApiClient extends AbstractApiClient { } } - static listenResponseToPlay = (listen: ListenResponse): PlayObject => { + static listenResponseToPlay(listen: ListenResponse): PlayObject { const { listened_at, track_metadata: { @@ -512,7 +512,7 @@ export class ListenbrainzApiClient extends AbstractApiClient { /** * Try to parse true artists and track name without using MB information * */ - static listenResponseToNaivePlay = (listen: ListenResponse): PlayObject => { + static listenResponseToNaivePlay(listen: ListenResponse): PlayObject { const { listened_at, recording_msid, @@ -581,7 +581,7 @@ export class ListenbrainzApiClient extends AbstractApiClient { } } - static formatPlayObj = (obj: any, options: FormatPlayObjectOptions): PlayObject => { + static formatPlayObj(obj: any, options: FormatPlayObjectOptions): PlayObject { return ListenbrainzApiClient.listenResponseToPlay(obj); } } diff --git a/src/backend/sources/ListenbrainzSource.ts b/src/backend/sources/ListenbrainzSource.ts index cd9b6b52..3946710c 100644 --- a/src/backend/sources/ListenbrainzSource.ts +++ b/src/backend/sources/ListenbrainzSource.ts @@ -34,7 +34,7 @@ export default class ListenbrainzSource extends MemorySource { this.logger.info(`Note: The player for this source is an analogue for the 'Now Playing' status exposed by ${this.type} which is NOT used for scrobbling. Instead, the 'recently played' or 'history' information provided by this source is used for scrobbles.`) } - static formatPlayObj = (obj: any, options: FormatPlayObjectOptions = {}) => ListenbrainzApiClient.formatPlayObj(obj, options); + static formatPlayObj(obj: any, options: FormatPlayObjectOptions = {}){ return ListenbrainzApiClient.formatPlayObj(obj, options); } protected async doCheckConnection(): Promise { try { -- 2.51.2 From ec897b185cf886c75a9422647e0f5c4b34c09563 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 19 Feb 2024 10:50:10 -0500 Subject: [PATCH 18/34] fix: Fix logger used for tests to always be 'noop' Cleans up logging output during tests --- src/backend/tests/jellyfin/jellyfin.test.ts | 2 +- src/backend/tests/player/player.test.ts | 2 +- src/backend/tests/scrobbler/TestScrobbler.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/tests/jellyfin/jellyfin.test.ts b/src/backend/tests/jellyfin/jellyfin.test.ts index 41e93712..c704a28d 100644 --- a/src/backend/tests/jellyfin/jellyfin.test.ts +++ b/src/backend/tests/jellyfin/jellyfin.test.ts @@ -28,7 +28,7 @@ describe('Jellyfin Payload Parsing', function () { describe('Correctly detects events as valid/invalid', function () { - const jfSource = new JellyfinSource('Test', {data: {}}, {localUrl: 'test', configDir: 'test', logger: getLogger({}, 'Test')}, new EventEmitter()); + const jfSource = new JellyfinSource('Test', {data: {}}, {localUrl: 'test', configDir: 'test', logger: getLogger({}, 'noop')}, new EventEmitter()); it('Should parse PlayProgress with Audio ItemType as valid event', async function () { const fixture = dataAsFixture(samplePayload[0]); diff --git a/src/backend/tests/player/player.test.ts b/src/backend/tests/player/player.test.ts index 27f8aade..c00cdb70 100644 --- a/src/backend/tests/player/player.test.ts +++ b/src/backend/tests/player/player.test.ts @@ -8,7 +8,7 @@ import { playObjDataMatch } from "../../utils.js"; import dayjs from "dayjs"; import clone from "clone"; -const logger = getLogger({}); +const logger = getLogger({}, 'noop'); const newPlay = generatePlay({duration: 300}); diff --git a/src/backend/tests/scrobbler/TestScrobbler.ts b/src/backend/tests/scrobbler/TestScrobbler.ts index 5acfe4be..355dae61 100644 --- a/src/backend/tests/scrobbler/TestScrobbler.ts +++ b/src/backend/tests/scrobbler/TestScrobbler.ts @@ -8,7 +8,7 @@ import request from "superagent"; export class TestScrobbler extends AbstractScrobbleClient { constructor() { - const logger = getLogger({}); + const logger = getLogger({}, 'noop'); const notifier = new Notifiers(new EventEmitter(), new EventEmitter(), new EventEmitter()); super('test', 'Test', {name: 'test'}, notifier, new EventEmitter(), logger); } -- 2.51.2 From 1c6407238ce0f3b90e1200d0a879e0045e1b99cc Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 19 Feb 2024 12:27:34 -0500 Subject: [PATCH 19/34] feat: Add initial linting config --- eslint.config.js | 33 ++ package-lock.json | 1152 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 4 + 3 files changed, 1141 insertions(+), 48 deletions(-) create mode 100644 eslint.config.js diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..573ddeb9 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,33 @@ +// @ts-check + +import eslint from '@eslint/js'; +import tsEslint from 'typescript-eslint'; +import arrow from 'eslint-plugin-prefer-arrow-functions'; + +export default tsEslint.config( + { + extends: [ + eslint.configs.recommended, + ...tsEslint.configs.recommended, + ], + files: ['src/backend/**/*.ts'], + plugins: { + "prefer-arrow-functions": arrow + }, + rules: { + 'no-useless-catch': 'off', + '@typescript-eslint/no-unused-vars': 'off', + "prefer-arrow-functions/prefer-arrow-functions": [ + "warn", + { + "allowNamedFunctions": false, + "classPropertiesAllowed": false, + "disallowPrototype": false, + "returnStyle": "unchanged", + "singleReturnOnly": false + } + ], + "arrow-body-style": ["warn", "as-needed"] + } + } +); diff --git a/package-lock.json b/package-lock.json index 9ae35294..79a15605 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,6 +79,7 @@ "devDependencies": { "@dbus-types/dbus": "^0.0.4", "@dbus-types/notifications": "^0.0.5", + "@eslint/js": "^8.56.0", "@faker-js/faker": "^8.1.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@testing-library/jest-dom": "^5.17.0", @@ -110,6 +111,8 @@ "chai": "^4.3.6", "chai-as-promised": "^7.1.1", "clone": "^2.1.2", + "eslint": "^8.56.0", + "eslint-plugin-prefer-arrow-functions": "^3.2.4", "jscodeshift": "^0.15.0", "mocha": "^10.3.0", "msw": "^2.1.2", @@ -117,6 +120,7 @@ "ts-essentials": "^9.1.2", "typedoc": "^0.25", "typescript": "^5.3.3", + "typescript-eslint": "^7.0.1", "typescript-json-schema": "~0.55", "vite": "^5.0.11" }, @@ -125,6 +129,15 @@ "npm": ">=9.1.0" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@adobe/css-tools": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", @@ -1396,6 +1409,133 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@faker-js/faker": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.0.tgz", @@ -1514,6 +1654,61 @@ "node": ">=0.3.0" } }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2615,6 +2810,12 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, + "node_modules/@types/semver": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", + "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", + "dev": true + }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", @@ -2703,6 +2904,316 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.1.tgz", + "integrity": "sha512-OLvgeBv3vXlnnJGIAgCLYKjgMEU+wBGj07MQ/nxAaON+3mLzX7mJbhRYrVGiVvFiXtwFlkcBa/TtmglHy0UbzQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/type-utils": "7.0.1", + "@typescript-eslint/utils": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.1.tgz", + "integrity": "sha512-8GcRRZNzaHxKzBPU3tKtFNing571/GwPBeCvmAUw0yBtfE2XVd0zFKJIMSWkHJcPQi0ekxjIts6L/rrZq5cxGQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/typescript-estree": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.1.tgz", + "integrity": "sha512-v7/T7As10g3bcWOOPAcbnMDuvctHzCFYCG/8R4bK4iYzdFqsZTbXGln0cZNVcwQcwewsYU2BJLay8j0/4zOk4w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.1.tgz", + "integrity": "sha512-YtT9UcstTG5Yqy4xtLiClm1ZpM/pWVGFnkAa90UfdkkZsR1eP2mR/1jbHeYp8Ay1l1JHPyGvoUYR6o3On5Nhmw==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.0.1", + "@typescript-eslint/utils": "7.0.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.1.tgz", + "integrity": "sha512-uJDfmirz4FHib6ENju/7cz9SdMSkeVvJDK3VcMFvf/hAShg8C74FW+06MaQPODHfDJp/z/zHfgawIJRjlu0RLg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.1.tgz", + "integrity": "sha512-SO9wHb6ph0/FN5OJxH4MiPscGah5wjOd0RRpaLvuBv9g8565Fgu0uMySFEPqwPHiQU90yzJ2FjRYKGrAhS1xig==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.1.tgz", + "integrity": "sha512-oe4his30JgPbnv+9Vef1h48jm0S6ft4mNwi9wj7bX10joGn07QRfqIqFHoMiajrtoU88cIhXf8ahwgrcbNLgPA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/typescript-estree": "7.0.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.1.tgz", + "integrity": "sha512-hwAgrOyk++RTXrP4KzCg7zB2U0xt7RUU0ZdMSCsqF3eKUwkdXUMyTb0qdCuji7VIbcpG62kKTU9M1J1c9UpFBw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.0.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@vitejs/plugin-react": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", @@ -2757,6 +3268,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", @@ -2942,6 +3462,15 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -3360,6 +3889,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -3973,6 +4511,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/default-require-extensions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", @@ -4131,11 +4675,35 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -4233,63 +4801,238 @@ "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", "dependencies": { - "es6-promise": "^4.0.3" + "es6-promise": "^4.0.3" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-prefer-arrow-functions": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow-functions/-/eslint-plugin-prefer-arrow-functions-3.2.4.tgz", + "integrity": "sha512-HbPmlbO/iYQeVs2fuShNkGVJDfVfgSd84Vzxv+xlh+nIVoSsZvTj6yOqszw4mtG9JbiqMShVWqbVeoVsejE59w==", + "dev": true, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" }, "engines": { - "node": ">=12" + "node": ">=8" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=6" + "node": "*" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { "node": ">=10" @@ -4298,6 +5041,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -4311,6 +5071,48 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -4496,6 +5298,18 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -4538,6 +5352,18 @@ "node": ">=0.8.0" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/file-stream-rotator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", @@ -4642,6 +5468,26 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "node_modules/flow-parser": { "version": "0.227.0", "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.227.0.tgz", @@ -4958,6 +5804,35 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -5010,6 +5885,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/graphql": { "version": "16.8.1", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", @@ -5216,6 +6097,15 @@ } ] }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -5236,6 +6126,31 @@ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==" }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -5547,6 +6462,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -6132,6 +7056,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -6261,6 +7191,19 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -6301,6 +7244,12 @@ "dev": true, "peer": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", @@ -6839,6 +7788,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -7406,6 +8361,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -7552,6 +8524,18 @@ "node": ">=8" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -7746,6 +8730,15 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -8012,6 +9005,15 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -8534,7 +9536,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -9441,6 +10442,12 @@ "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -9537,6 +10544,18 @@ "node": ">= 14.0.0" } }, + "node_modules/ts-api-utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-essentials": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.4.1.tgz", @@ -9638,6 +10657,18 @@ "fsevents": "~2.3.3" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -9734,6 +10765,31 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.0.1.tgz", + "integrity": "sha512-aIquOfwHkGHrMSH57HxLT+1Qzp99YtGxEHXMRD+BXOc8fkuFBbA5BXsMYnoVXFuXOWBdXg8U2rN9Xe4p7LrPSQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "7.0.1", + "@typescript-eslint/parser": "7.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/typescript-json-schema": { "version": "0.55.0", "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.55.0.tgz", diff --git a/package.json b/package.json index ad6e4e86..bf0577a1 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "devDependencies": { "@dbus-types/dbus": "^0.0.4", "@dbus-types/notifications": "^0.0.5", + "@eslint/js": "^8.56.0", "@faker-js/faker": "^8.1.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@testing-library/jest-dom": "^5.17.0", @@ -144,6 +145,8 @@ "chai": "^4.3.6", "chai-as-promised": "^7.1.1", "clone": "^2.1.2", + "eslint": "^8.56.0", + "eslint-plugin-prefer-arrow-functions": "^3.2.4", "jscodeshift": "^0.15.0", "mocha": "^10.3.0", "msw": "^2.1.2", @@ -151,6 +154,7 @@ "ts-essentials": "^9.1.2", "typedoc": "^0.25", "typescript": "^5.3.3", + "typescript-eslint": "^7.0.1", "typescript-json-schema": "~0.55", "vite": "^5.0.11" }, -- 2.51.2 From a62fe705f91876af0ab637e074e205db115c83b3 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 19 Feb 2024 13:02:28 -0500 Subject: [PATCH 20/34] fix: Fix lint errors --- eslint.config.js | 4 +- src/backend/common/infrastructure/Atomic.ts | 2 +- .../typings/lastfm-node-client.d.ts | 1 - src/backend/common/logging.ts | 35 +++++----------- src/backend/common/vendor/JRiverApiClient.ts | 2 +- src/backend/common/vendor/KodiApiClient.ts | 8 ++-- src/backend/common/vendor/LastfmApiClient.ts | 3 +- .../common/vendor/ListenbrainzApiClient.ts | 4 +- .../scrobblers/AbstractScrobbleClient.ts | 26 ++++-------- src/backend/scrobblers/LastfmScrobbler.ts | 6 +-- .../scrobblers/ListenbrainzScrobbler.ts | 4 +- src/backend/scrobblers/MalojaScrobbler.ts | 6 +-- src/backend/scrobblers/ScrobbleClients.ts | 18 +++----- src/backend/server/api.ts | 30 +++++++------ src/backend/server/auth.ts | 6 +-- src/backend/server/deezerRoutes.ts | 6 +-- src/backend/server/jellyfinRoutes.ts | 8 ++-- src/backend/server/plexRoutes.ts | 6 +-- src/backend/server/tautulliRoutes.ts | 2 +- src/backend/server/webscrobblerRoutes.ts | 4 +- src/backend/sources/AbstractSource.ts | 37 +++++----------- src/backend/sources/ChromecastSource.ts | 15 +++---- src/backend/sources/DeezerSource.ts | 18 +++----- src/backend/sources/JRiverSource.ts | 4 +- src/backend/sources/JellyfinSource.ts | 8 +--- src/backend/sources/KodiSource.ts | 2 +- src/backend/sources/LastfmSource.ts | 4 +- src/backend/sources/ListenbrainzSource.ts | 4 +- src/backend/sources/MPRISSource.ts | 14 +++---- src/backend/sources/MemorySource.ts | 16 +++---- src/backend/sources/MopidySource.ts | 2 +- .../PlayerState/AbstractPlayerState.ts | 6 +-- src/backend/sources/PlexSource.ts | 15 +++---- src/backend/sources/ScrobbleSources.ts | 21 ++++------ src/backend/sources/SpotifySource.ts | 42 ++++--------------- src/backend/sources/SubsonicSource.ts | 10 ++--- src/backend/sources/TautulliSource.ts | 9 +--- src/backend/sources/WebScrobblerSource.ts | 4 +- src/backend/sources/YTMusicSource.ts | 15 ++----- .../ingressNotifiers/TautulliNotifier.ts | 2 +- .../tests/listenbrainz/listenbrainz.test.ts | 2 +- src/backend/tests/utils/interfaces.ts | 2 +- src/backend/utils.ts | 19 ++++----- src/backend/utils/MDNSUtils.ts | 4 +- src/backend/utils/StringUtils.ts | 20 ++++----- src/backend/utils/TimeUtils.ts | 10 +++-- 46 files changed, 178 insertions(+), 308 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 573ddeb9..7b649147 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -11,6 +11,7 @@ export default tsEslint.config( ...tsEslint.configs.recommended, ], files: ['src/backend/**/*.ts'], + ignores: ['eslint.config.js'], plugins: { "prefer-arrow-functions": arrow }, @@ -27,7 +28,8 @@ export default tsEslint.config( "singleReturnOnly": false } ], - "arrow-body-style": ["warn", "as-needed"] + "arrow-body-style": ["warn", "as-needed"], + "@typescript-eslint/no-explicit-any": "warn" } } ); diff --git a/src/backend/common/infrastructure/Atomic.ts b/src/backend/common/infrastructure/Atomic.ts index 06195c53..0909f857 100644 --- a/src/backend/common/infrastructure/Atomic.ts +++ b/src/backend/common/infrastructure/Atomic.ts @@ -205,7 +205,7 @@ export interface numberFormatOptions { } } -export const DELIMITERS = [',','&','\/','\\']; +export const DELIMITERS = [',','&','/','\\']; export const ARTIST_WEIGHT = 0.3; export const TITLE_WEIGHT = 0.4; 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 3f0a7304..8ee66f5c 100644 --- a/src/backend/common/infrastructure/typings/lastfm-node-client.d.ts +++ b/src/backend/common/infrastructure/typings/lastfm-node-client.d.ts @@ -103,7 +103,6 @@ declare module 'lastfm-node-client' { }, duration: number, date?: { - // @ts-ignore uts: number, }, '@attr'?: { diff --git a/src/backend/common/logging.ts b/src/backend/common/logging.ts index 36d26604..346840cd 100644 --- a/src/backend/common/logging.ts +++ b/src/backend/common/logging.ts @@ -60,7 +60,7 @@ export const getLogger = (config: LogConfig = {}, name = 'app'): winstonNs.Logge const myTransports: TransportStream[] = [ new DuplexTransport({ stream: { - transform(chunk, e, cb) { + transform: (chunk, e, cb) => { cb(null, chunk); }, objectMode: true, @@ -90,10 +90,9 @@ export const getLogger = (config: LogConfig = {}, name = 'app'): winstonNs.Logge try { fileOrDirectoryIsWriteable(logPath); - // @ts-ignore myTransports.push(rotateTransport); } catch (e: any) { - let msg = 'WILL NOT write logs to rotating file due to an error while trying to access the specified logging directory'; + const msg = 'WILL NOT write logs to rotating file due to an error while trying to access the specified logging directory'; errors.push(new ErrorWithCause(msg, {cause: e})); } } @@ -156,7 +155,7 @@ export const defaultFormat = (defaultLabel = 'App') => printf(({ ...rest }) => { const keys = Object.keys(rest); - let stringifyValue = keys.length > 0 && !keys.every(x => causeKeys.some(y => y == x)) ? stringify.default(rest) : ''; + const stringifyValue = keys.length > 0 && !keys.every(x => causeKeys.some(y => y == x)) ? stringify.default(rest) : ''; let msg = message; let stackMsg = ''; if (stack !== undefined) { @@ -174,7 +173,7 @@ export const defaultFormat = (defaultLabel = 'App') => printf(({ } } - let nodes = Array.isArray(labels) ? labels : [labels]; + const nodes = Array.isArray(labels) ? labels : [labels]; if (leaf !== null && leaf !== undefined && !nodes.includes(leaf)) { nodes.push(leaf); } @@ -221,7 +220,6 @@ export const logLevels = { export const LOG_LEVEL_REGEX: RegExp = /\s*(debug|warn|info|error|verbose)\s*:/i export const isLogLineMinLevel = (log: string | LogInfo, minLevelText: LogLevel): boolean => { - // @ts-ignore const minLevel = logLevels[minLevelText]; let level: number; @@ -230,19 +228,15 @@ export const isLogLineMinLevel = (log: string | LogInfo, minLevelText: LogLevel) if (lineLevelMatch === null) { return false; } - // @ts-ignore level = logLevels[lineLevelMatch[1]]; } else { const lineLevelMatch = log.level; - // @ts-ignore level = logLevels[lineLevelMatch]; } return level <= minLevel; } -export const isLogLevelMinLevel = (levelStr: LogLevel, minLevelStr: LogLevel): boolean => { - return logLevels[levelStr] <= logLevels[minLevelStr]; -} +export const isLogLevelMinLevel = (levelStr: LogLevel, minLevelStr: LogLevel): boolean => logLevels[levelStr] <= logLevels[minLevelStr] const isProbablyError = (val: any, explicitErrorName?: string) => { if(typeof val !== 'object' || val === null) { @@ -281,12 +275,10 @@ const errorAwareFormat = { if (isProbablyError(einfo)) { const tinfo = transformError(einfo); info = Object.assign({}, tinfo, { - // @ts-ignore level: einfo.level, - // @ts-ignore [LEVEL]: einfo[LEVEL] || einfo.level, message: tinfo.message, - // @ts-ignore + [MESSAGE]: tinfo[MESSAGE] || tinfo.message }); if(includeStack) { @@ -294,20 +286,17 @@ const errorAwareFormat = { const dummyErr = new ErrorWithCause(''); const names = Object.getOwnPropertyNames(tinfo); for(const k of names) { + // eslint-disable-next-line no-prototype-builtins if(dummyErr.hasOwnProperty(k) || k === 'cause') { - // @ts-ignore dummyErr[k] = tinfo[k]; } } - // @ts-ignore info.stack = stackWithCauses(dummyErr); } } else { const err = transformError(einfo.message); info = Object.assign({}, einfo, err); - // @ts-ignore info.message = err.message; - // @ts-ignore info[MESSAGE] = err.message; if(includeStack) { @@ -316,12 +305,11 @@ const errorAwareFormat = { // https://stackoverflow.com/a/18278145/1469797 const names = Object.getOwnPropertyNames(err); for(const k of names) { + // eslint-disable-next-line no-prototype-builtins if(dummyErr.hasOwnProperty(k) || k === 'cause') { - // @ts-ignore dummyErr[k] = err[k]; } } - // @ts-ignore info.stack = stackWithCauses(dummyErr); } } @@ -350,14 +338,13 @@ const _transformError = (err: Error, seen: Set) => { try { - // @ts-ignore - let mOpts = err.matchOptions ?? matchOptions; + // @ts-expect-error type missing expected props + const mOpts = err.matchOptions ?? matchOptions; - // @ts-ignore const cause = err.cause as unknown; if (cause !== undefined && cause instanceof Error) { - // @ts-ignore + // @ts-expect-error type missing expected props err.cause = _transformError(cause, seen, mOpts); } diff --git a/src/backend/common/vendor/JRiverApiClient.ts b/src/backend/common/vendor/JRiverApiClient.ts index 06ec919f..848a5420 100644 --- a/src/backend/common/vendor/JRiverApiClient.ts +++ b/src/backend/common/vendor/JRiverApiClient.ts @@ -140,7 +140,7 @@ export class JRiverApiClient extends AbstractApiClient { testAuth = async () => { try { - let req = request.get(`${this.url}Authenticate`); + const req = request.get(`${this.url}Authenticate`); if (this.config.username !== undefined) { req.auth(this.config.username, this.config.password); } diff --git a/src/backend/common/vendor/KodiApiClient.ts b/src/backend/common/vendor/KodiApiClient.ts index b4d5f701..acde4e7e 100644 --- a/src/backend/common/vendor/KodiApiClient.ts +++ b/src/backend/common/vendor/KodiApiClient.ts @@ -99,8 +99,8 @@ export class KodiApiClient extends AbstractApiClient { playerid, } = obj; - let artists = artistVal === null || artistVal === undefined ? [] : artistVal; - let album = albumVal === null || albumVal === '' ? undefined : albumVal; + const artists = artistVal === null || artistVal === undefined ? [] : artistVal; + const album = albumVal === null || albumVal === '' ? undefined : albumVal; const trackProgressPosition = time !== undefined ? Math.round(dayjs.duration(time).asSeconds()) : undefined; return { @@ -150,14 +150,14 @@ export class KodiApiClient extends AbstractApiClient { getPlayerInfo = async (id: number): Promise => { // https://kodi.wiki/view/JSON-RPC_API/v12#Player.GetProperties - // @ts-ignore + // @ts-expect-error types are wrong const playerInfo = await this.client.Player.GetProperties(0, ["position","type","time","totaltime"]) return playerInfo; } getPlayerItem = async (id: number): Promise<{item: PlayerItem}> => { // https://kodi.wiki/view/JSON-RPC_API/v12#Player.GetItem - // @ts-ignore + // @ts-expect-error types are wrong const itemInfo = await this.client.Player.GetItem(0, ["title","artist","album","albumartist","starttime","endtime","duration","streamdetails","uniqueid"]); return itemInfo as {item: PlayerItem}; } diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index 0803fd47..75b13c3a 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -61,7 +61,6 @@ export default class LastfmApiClient extends AbstractApiClient { }, duration, date: { - // @ts-ignore uts: time, } = {}, '@attr': { @@ -71,7 +70,7 @@ export default class LastfmApiClient extends AbstractApiClient { mbid, } = obj; // arbitrary decision yikes - let artistStrings = splitByFirstFound(artists, [','], [artistName]); + const artistStrings = splitByFirstFound(artists, [','], [artistName]); return { data: { artists: [...new Set(artistStrings)] as string[], diff --git a/src/backend/common/vendor/ListenbrainzApiClient.ts b/src/backend/common/vendor/ListenbrainzApiClient.ts index 2888f0bf..65752229 100644 --- a/src/backend/common/vendor/ListenbrainzApiClient.ts +++ b/src/backend/common/vendor/ListenbrainzApiClient.ts @@ -150,7 +150,7 @@ export class ListenbrainzApiClient extends AbstractApiClient { if(status !== undefined) { const msgParts = [`(HTTP Status ${status})`]; // if the response is 400 then its likely there was an issue with the data we sent rather than an error with the service - let showStopper = status !== 400; + const showStopper = status !== 400; if(body !== undefined) { if(typeof body === 'object') { if('code' in body) { @@ -392,7 +392,7 @@ export class ListenbrainzApiClient extends AbstractApiClient { } // now try to extract any remaining artists from filtered artist/name values - let parsedArtists = parseArtistCredits(filteredSubmittedArtistName); + const parsedArtists = parseArtistCredits(filteredSubmittedArtistName); if (parsedArtists !== undefined) { if (parsedArtists.primary !== undefined) { artistsFromUserValues.push(parsedArtists.primary); diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index a31dbf7a..36a55c9c 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -172,17 +172,11 @@ export default abstract class AbstractScrobbleClient implements Authenticatable return true; } - authGated = () => { - return this.requiresAuth && !this.authed; - } + authGated = () => this.requiresAuth && !this.authed - canTryAuth = () => { - return this.authGated() && this.authFailure !== true; - } + canTryAuth = () => this.authGated() && this.authFailure !== true - protected doAuthentication = async (): Promise => { - return this.authed; - } + protected doAuthentication = async (): Promise => this.authed // default init function, should be overridden if auth stage is required testAuth = async () => { @@ -197,18 +191,14 @@ export default abstract class AbstractScrobbleClient implements Authenticatable } } - isReady = async () => { - return this.initialized && !this.authGated(); - } + isReady = async () => this.initialized && !this.authGated() refreshScrobbles = async () => { this.logger.debug('Scrobbler does not have refresh function implemented!'); } public abstract alreadyScrobbled(playObj: PlayObject, log?: boolean): Promise; - scrobblesLastCheckedAt = () => { - return this.lastScrobbleCheck; - } + scrobblesLastCheckedAt = () => this.lastScrobbleCheck formatPlayObj = (obj: any, options: FormatPlayObjectOptions = {}) => { this.logger.warn('formatPlayObj should be defined by concrete class!'); @@ -246,9 +236,7 @@ export default abstract class AbstractScrobbleClient implements Authenticatable this.scrobbledPlayObjs = new FixedSizeList(this.MAX_STORED_SCROBBLES, this.scrobbledPlayObjs.data.filter(x => this.timeFrameIsValid(x.play)[0])) ; } - getScrobbledPlays = () => { - return this.scrobbledPlayObjs.data.map(x => x.scrobble); - } + getScrobbledPlays = () => this.scrobbledPlayObjs.data.map(x => x.scrobble) findExistingSubmittedPlayObj = (playObj: PlayObject): ([undefined, undefined] | [ScrobbledPlayObject, ScrobbledPlayObject[]]) => { const { @@ -392,7 +380,7 @@ export default abstract class AbstractScrobbleClient implements Authenticatable artistBreakdown = `Artist: (${artistMatch.toFixed(2)} + Whole Match Bonus ${artistWholeMatchBonus.toFixed(2)}) * (${ARTIST_WEIGHT} + Whole Match Bonus 0.05) = ${artistScore.toFixed(2)}`; } - let scoreBreakdowns = [ + const scoreBreakdowns = [ //`Reference: ${(referenceMatch ? 1 : 0)} * ${REFERENCE_WEIGHT} = ${referenceScore.toFixed(2)}`, artistBreakdown, `Title: ${titleMatch.toFixed(2)} * ${TITLE_WEIGHT} = ${titleScore.toFixed(2)}`, diff --git a/src/backend/scrobblers/LastfmScrobbler.ts b/src/backend/scrobblers/LastfmScrobbler.ts index e0444596..87538e4f 100644 --- a/src/backend/scrobblers/LastfmScrobbler.ts +++ b/src/backend/scrobblers/LastfmScrobbler.ts @@ -32,7 +32,7 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { constructor(name: any, config: LastfmClientConfig, options = {}, notifier: Notifiers, emitter: EventEmitter, logger: Logger) { super('lastfm', name, config, notifier, emitter, logger); - // @ts-ignore + // @ts-expect-error sloppy data structure assign this.api = new LastfmApiClient(name, config.data, options) } @@ -131,9 +131,7 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { return track.toLocaleLowerCase().trim(); } - alreadyScrobbled = async (playObj: PlayObject, log = false) => { - return (await this.existingScrobble(playObj)) !== undefined; - } + alreadyScrobbled = async (playObj: PlayObject, log = false) => (await this.existingScrobble(playObj)) !== undefined public playToClientPayload(playObject: PlayObject): object { return this.api.playToClientPayload(playObject); diff --git a/src/backend/scrobblers/ListenbrainzScrobbler.ts b/src/backend/scrobblers/ListenbrainzScrobbler.ts index a284822b..d323e996 100644 --- a/src/backend/scrobblers/ListenbrainzScrobbler.ts +++ b/src/backend/scrobblers/ListenbrainzScrobbler.ts @@ -77,9 +77,7 @@ export default class ListenbrainzScrobbler extends AbstractScrobbleClient { this.lastScrobbleCheck = dayjs(); } - alreadyScrobbled = async (playObj: PlayObject, log = false) => { - return (await this.existingScrobble(playObj)) !== undefined; - } + alreadyScrobbled = async (playObj: PlayObject, log = false) => (await this.existingScrobble(playObj)) !== undefined public playToClientPayload(playObj: PlayObject): ListenPayload { return ListenbrainzApiClient.playToListenPayload(playObj); diff --git a/src/backend/scrobblers/MalojaScrobbler.ts b/src/backend/scrobblers/MalojaScrobbler.ts index f6755c3f..c0ead014 100644 --- a/src/backend/scrobblers/MalojaScrobbler.ts +++ b/src/backend/scrobblers/MalojaScrobbler.ts @@ -106,7 +106,7 @@ export default class MalojaScrobbler extends AbstractScrobbleClient { duration = mDuration; time = mTime; } - let artistStrings = artists.reduce((acc: any, curr: any) => { + const artistStrings = artists.reduce((acc: any, curr: any) => { let aString; if (typeof curr === 'string') { aString = curr; @@ -375,9 +375,7 @@ export default class MalojaScrobbler extends AbstractScrobbleClient { return lowerTitle; } - alreadyScrobbled = async (playObj: any, log = false) => { - return (await this.existingScrobble(playObj)) !== undefined; - } + alreadyScrobbled = async (playObj: any, log = false) => (await this.existingScrobble(playObj)) !== undefined public playToClientPayload(playObj: PlayObject): MalojaScrobbleRequestData { diff --git a/src/backend/scrobblers/ScrobbleClients.ts b/src/backend/scrobblers/ScrobbleClients.ts index 5011058e..b597d984 100644 --- a/src/backend/scrobblers/ScrobbleClients.ts +++ b/src/backend/scrobblers/ScrobbleClients.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-case-declarations */ import dayjs, {Dayjs} from "dayjs"; import { createAjvFactory, @@ -55,13 +56,9 @@ export default class ScrobbleClients { }); } - getByName = (name: any) => { - return this.clients.find(x => x.name === name); - } + getByName = (name: any) => this.clients.find(x => x.name === name) - getByType = (type: any) => { - return this.clients.filter(x => x.type === type); - } + getByType = (type: any) => this.clients.filter(x => x.type === type) async getStatusSummary(type?: string, name?: string): Promise<[boolean, string[]]> { let clients: AbstractScrobbleClient[]; @@ -88,7 +85,7 @@ export default class ScrobbleClients { } buildClientsFromConfig = async (notifier: Notifiers) => { - let configs: ParsedConfig[] = []; + const configs: ParsedConfig[] = []; let configFile; try { @@ -127,7 +124,7 @@ export default class ScrobbleClients { } for (const clientType of clientTypes) { - let defaultConfigureAs = 'client'; + const defaultConfigureAs = 'client'; switch (clientType) { case 'maloja': // env builder for single user mode @@ -142,7 +139,6 @@ export default class ScrobbleClients { configureAs: 'client', data: { url, - // @ts-ignore apiKey } }) @@ -162,7 +158,6 @@ export default class ScrobbleClients { source: 'ENV', mode: 'single', configureAs: 'client', - // @ts-ignore data: {...lfm, redirectUri: lfm.redirectUri ?? `${this.localUrl}/lastfm/callback`} }) } @@ -180,7 +175,6 @@ export default class ScrobbleClients { source: 'ENV', mode: 'single', configureAs: 'client', - // @ts-ignore data: lz }) } @@ -211,7 +205,7 @@ export default class ScrobbleClients { for(const [i,rawConf] of rawClientConfigs.entries()) { try { const validConfig = validateJson(rawConf, clientSchema, this.logger); - // @ts-ignore + // @ts-expect-error configureAs should exist const {configureAs = defaultConfigureAs} = validConfig; if (configureAs === 'client') { const parsedConfig: ParsedConfig = { diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index b9b7d838..27e64dfe 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -81,7 +81,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput let logObjectStream: Transform; try { logObjectStream = new Transform({ - transform(chunk, e, cb) { + transform: (chunk, e, cb) => { cb(null, chunk) }, objectMode: true, @@ -109,13 +109,13 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput const sourceRequiredMiddle = sourceMiddleFunc(true); const setLogWebSettings: ExpressHandler = async (req, res, next) => { - // @ts-ignore + // @ts-expect-error logLevel not part of session const sessionLevel: LogLevel | undefined = req.session.logLevel as LogLevel | undefined; if(sessionLevel !== undefined && logConfig.level !== sessionLevel) { logConfig.level = sessionLevel; } - // @ts-ignore - const sessionLimit: number | undefined = req.session.limit as Number | undefined; + // @ts-expect-error limit not part of session + const sessionLimit: number | undefined = req.session.limit as number | undefined; if(sessionLimit !== undefined && logConfig.limit !== sessionLimit) { logConfig.limit = sessionLimit; } @@ -137,9 +137,9 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput logConfig.level = req.body.level as LogLevel | undefined ?? logConfig.level; logConfig.limit = req.body.limit ?? logConfig.limit; const slicedLog = getLogs(logConfig.level, logConfig.limit + 1, logConfig.sort === 'ascending' ? 'asc' : 'desc'); - // @ts-ignore + // @ts-expect-error logLevel not part of session req.session.logLevel = logConfig.level; - // @ts-ignore + // @ts-expect-error limit not part of session req.session.limit = logConfig.limit; const jsonLogs: LogInfoJson[] = slicedLog.map(x => ({...x, formattedMessage: x[MESSAGE]})); return res.json({data: jsonLogs, settings: logConfig}); @@ -295,7 +295,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput scrobbleClient: client, } = req; - let result: DeadLetterScrobble[] = (client as AbstractScrobbleClient).deadLetterScrobbles; + const result: DeadLetterScrobble[] = (client as AbstractScrobbleClient).deadLetterScrobbles; return res.json(result); }); @@ -310,7 +310,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput await (client as AbstractScrobbleClient).processDeadLetterQueue(1000); - let result: DeadLetterScrobble[] = (client as AbstractScrobbleClient).deadLetterScrobbles; + const result: DeadLetterScrobble[] = (client as AbstractScrobbleClient).deadLetterScrobbles; return res.json(result); }); @@ -383,7 +383,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput app.getAsync('/api/scrobbled', clientMiddleFunc(false), async (req, res, next) => { const { - // @ts-ignore + // @ts-expect-error scrobbleClient not part of req scrobbleClient: client, } = req; @@ -396,7 +396,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput }); app.use('/api/poll', sourceRequiredMiddle); - app.getAsync('/api/poll', async function (req, res) { + app.getAsync('/api/poll', async (req, res) => { // @ts-expect-error TS(2339): Property 'scrobbleSource' does not exist on type '... Remove this comment to see the full error message const source = req.scrobbleSource as AbstractSource; source.logger.debug('User requested (re)start via API call'); @@ -419,7 +419,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput }); app.use('/api/client/init', clientRequiredMiddle); - app.postAsync('/api/client/init', async function (req, res) { + app.postAsync('/api/client/init', async (req, res) => { // @ts-expect-error TS(2339): Property 'scrobbleSource' does not exist on type '... Remove this comment to see the full error message const client = req.scrobbleClient as AbstractScrobbleClient; client.logger.debug('User requested (re)start via API call'); @@ -434,10 +434,8 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput res.status(200).send('OK'); }); - app.getAsync('/health', async function(req, res) { - return res.redirect(307, `/api/${req.url.slice(1)}`); - }); - app.getAsync('/api/health', async function (req, res) { + app.getAsync('/health', async (req, res) => res.redirect(307, `/api/${req.url.slice(1)}`)); + app.getAsync('/api/health', async (req, res) => { const { type, name @@ -450,7 +448,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput return res.status((clientsReady && sourcesReady) ? 200 : 500).json({messages: sourceMessages.concat(clientMessages)}); }); - app.useAsync('/api/*', async function (req, res) { + app.useAsync('/api/*', async (req, res) => { const remote = req.connection.remoteAddress; const proxyRemote = req.headers["x-forwarded-for"]; const ua = req.headers["user-agent"]; diff --git a/src/backend/server/auth.ts b/src/backend/server/auth.ts index fd2e2fc0..724cb99b 100644 --- a/src/backend/server/auth.ts +++ b/src/backend/server/auth.ts @@ -10,7 +10,7 @@ import SpotifySource from "../sources/SpotifySource.js"; export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMiddle: ExpressHandler, clientMiddle: ExpressHandler, scrobbleSources: ScrobbleSources, scrobbleClients: ScrobbleClients) => { app.use('/api/client/auth', clientMiddle); - app.getAsync('/api/client/auth', async function (req, res) { + app.getAsync('/api/client/auth', async (req, res) => { const { scrobbleClient, } = req as any; @@ -25,7 +25,7 @@ export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMid }); app.use('/api/source/auth', sourceMiddle); - app.getAsync('/api/source/auth', async function (req, res, next) { + app.getAsync('/api/source/auth', async (req, res, next) => { const { // @ts-expect-error TS(2339): Property 'scrobbleSource' does not exist on type '... Remove this comment to see the full error message scrobbleSource: source, @@ -54,7 +54,7 @@ export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMid } }); - app.getAsync(/.*callback$/, async function (req, res, next) { + app.getAsync(/.*callback$/, async (req, res, next) => { if(req.url.indexOf('/api') !== 0) { return res.redirect(307, `/api${req.url}`); } diff --git a/src/backend/server/deezerRoutes.ts b/src/backend/server/deezerRoutes.ts index 647794df..d4b4ae81 100644 --- a/src/backend/server/deezerRoutes.ts +++ b/src/backend/server/deezerRoutes.ts @@ -18,7 +18,7 @@ export const setupDeezerRoutes = (app: ExpressWithAsync, logger: Logger, scrobbl // something about the deezer passport strategy makes express continue with the response even though it should wait for accesstoken callback and userprofile fetching // so to get around this add an additional middleware that loops/sleeps until we should have fetched everything ¯\_(ツ)_/¯ - app.getAsync(/.*deezer\/callback*$/, function (req, res, next) { + app.getAsync(/.*deezer\/callback*$/, (req, res, next) => { if(req.url.indexOf('/api') !== 0) { return res.redirect(307, `/api${req.url}`); } @@ -26,9 +26,9 @@ export const setupDeezerRoutes = (app: ExpressWithAsync, logger: Logger, scrobbl const entity = scrobbleSources.getByName(req.session.deezerSource as string); const passportFunc = passport.authenticate(`deezer-${entity.name}`, {session: false}); return passportFunc(req, res, next); - }, async function (req, res) { + }, async (req, res) => { // @ts-expect-error TS(2339): Property 'deezerSource' does not exist on type 'Se... Remove this comment to see the full error message - let entity = scrobbleSources.getByName(req.session.deezerSource as string) as DeezerSource; + const entity = scrobbleSources.getByName(req.session.deezerSource as string) as DeezerSource; for(let i = 0; i < 3; i++) { if(entity.error !== undefined) { return res.send('Error with deezer credentials storage'); diff --git a/src/backend/server/jellyfinRoutes.ts b/src/backend/server/jellyfinRoutes.ts index f33939ac..8ae3b81a 100644 --- a/src/backend/server/jellyfinRoutes.ts +++ b/src/backend/server/jellyfinRoutes.ts @@ -18,17 +18,17 @@ export const setupJellyfinRoutes = (app: ExpressWithAsync, logger: Logger, scrob // } }); const jellyIngress = new JellyfinNotifier(); - app.postAsync('/jellyfin', async function(req, res) { + app.postAsync('/jellyfin', async (req, res) => { res.redirect(307, '/api/jellyfin/ingress'); }); app.postAsync('/api/jellyfin/ingress', - async function (req, res, next) { + async (req, res, next) => { // track request before parsing body to ensure we at least log that something is happening // (in the event body parsing does not work or request is not POST/PATCH) jellyIngress.trackIngress(req, true); next(); }, - jellyfinJsonParser, async function (req, res) { + jellyfinJsonParser, async (req, res) => { jellyIngress.trackIngress(req, false); res.send('OK'); @@ -59,8 +59,6 @@ export const setupJellyfinRoutes = (app: ExpressWithAsync, logger: Logger, scrob } const logPayload = pSources.some(x => { const { - data: { - } = {}, options: { logPayload = parseBool(process.env.DEBUG_MODE) } = {} diff --git a/src/backend/server/plexRoutes.ts b/src/backend/server/plexRoutes.ts index f0fe6624..0ef26f99 100644 --- a/src/backend/server/plexRoutes.ts +++ b/src/backend/server/plexRoutes.ts @@ -11,13 +11,13 @@ export const setupPlexRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleS const plexMiddle = plexRequestMiddle(); const plexLog = logger.child({labels: ['Plex Request']}, mergeArr); const plexIngress = new PlexNotifier(); - const plexIngressMiddle: ExpressHandler = async function (req, res, next) { + const plexIngressMiddle: ExpressHandler = async (req, res, next) => { // track request before parsing body to ensure we at least log that something is happening // (in the event body parsing does not work or request is not POST/PATCH) plexIngress.trackIngress(req, true); next(); }; - const plexIngressRoute: ExpressHandler = async function (req, res) { + const plexIngressRoute: ExpressHandler = async (req, res) => { plexIngress.trackIngress(req, false); const {payload} = req as any; @@ -36,7 +36,7 @@ export const setupPlexRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleS res.send('OK'); }; - app.postAsync('/plex', async function (req, res) { + app.postAsync('/plex', async (req, res) => { res.redirect(307, '/api/plex/ingress'); }); app.postAsync('/api/plex/ingress', plexIngressMiddle, plexMiddle, plexIngressRoute); diff --git a/src/backend/server/tautulliRoutes.ts b/src/backend/server/tautulliRoutes.ts index 9dedf245..01d32e06 100644 --- a/src/backend/server/tautulliRoutes.ts +++ b/src/backend/server/tautulliRoutes.ts @@ -43,7 +43,7 @@ export const setupTautulliRoutes = (app: ExpressWithAsync, logger: Logger, scrob res.send('OK'); }; - app.postAsync('/tautulli', async function(req, res) { + app.postAsync('/tautulli', async (req, res) => { res.redirect(307, '/api/tautulli/ingress'); }); app.postAsync('/api/tautulli/ingress', tautulliIngressRoute); diff --git a/src/backend/server/webscrobblerRoutes.ts b/src/backend/server/webscrobblerRoutes.ts index 815e86e0..174a81c2 100644 --- a/src/backend/server/webscrobblerRoutes.ts +++ b/src/backend/server/webscrobblerRoutes.ts @@ -23,13 +23,13 @@ export const setupWebscrobblerRoutes = (app: ExpressWithAsync, parentLogger: Log }); const webhookIngress = new WebhookNotifier(); app.postAsync('/api/webscrobbler*', - async function (req, res, next) { + async (req, res, next) => { // track request before parsing body to ensure we at least log that something is happening // (in the event body parsing does not work or request is not POST/PATCH) webhookIngress.trackIngress(req, true); next(); }, - webScrobblerJsonParser, nonEmptyBody(logger, 'WebScrobbler Extension'), async function (req, res) { + webScrobblerJsonParser, nonEmptyBody(logger, 'WebScrobbler Extension'), async (req, res) => { webhookIngress.trackIngress(req, false); res.sendStatus(200); diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index e8c0cc96..032f40ee 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -182,17 +182,11 @@ export default abstract class AbstractSource implements Authenticatable { return; } - authGated = () => { - return this.requiresAuth && !this.authed; - } + authGated = () => this.requiresAuth && !this.authed - canTryAuth = () => { - return this.authGated() && this.authFailure !== true; - } + canTryAuth = () => this.authGated() && this.authFailure !== true - protected doAuthentication = async (): Promise => { - return this.authed; - } + protected doAuthentication = async (): Promise => this.authed testAuth = async () => { if(!this.requiresAuth) { @@ -218,9 +212,7 @@ export default abstract class AbstractSource implements Authenticatable { && !this.authGated(); } - getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => { - return []; - } + getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => [] getUpstreamRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => { throw new Error('Not implemented'); @@ -233,9 +225,7 @@ export default abstract class AbstractSource implements Authenticatable { // by default if the track was recently played it is valid // this is useful for sources where the track doesn't have complete information like Subsonic // TODO make this more descriptive? or move it elsewhere - recentlyPlayedTrackIsValid = (playObj: PlayObject) => { - return true; - } + recentlyPlayedTrackIsValid = (playObj: PlayObject) => true protected addPlayToDiscovered = (play: PlayObject) => { const platformId = this.multiPlatform ? genGroupId(play) : SINGLE_USER_PLATFORM_ID; @@ -247,10 +237,9 @@ export default abstract class AbstractSource implements Authenticatable { this.emitEvent('discovered', {play}); } - getFlatRecentlyDiscoveredPlays = (): PlayObject[] => { - // @ts-ignore - return Array.from(this.recentDiscoveredPlays.values()).map(x => x.data).flat(3).sort(sortByNewestPlayDate); - } + getFlatRecentlyDiscoveredPlays = (): PlayObject[] => + Array.from(this.recentDiscoveredPlays.values()).map(x => x.data).flat(3).sort(sortByNewestPlayDate) + getRecentlyDiscoveredPlaysByPlatform = (platformId: PlayPlatformId): PlayObject[] => { const list = this.recentDiscoveredPlays.get(platformId); @@ -263,7 +252,7 @@ export default abstract class AbstractSource implements Authenticatable { } existingDiscovered = (play: PlayObject, opts: {checkAll?: boolean} = {}): PlayObject | undefined => { - let lists: PlayObject[][] = []; + const lists: PlayObject[][] = []; if(opts.checkAll !== true) { lists.push(this.getRecentlyDiscoveredPlaysByPlatform(this.multiPlatform ? genGroupId(play) : SINGLE_USER_PLATFORM_ID)); } else { @@ -366,13 +355,9 @@ export default abstract class AbstractSource implements Authenticatable { this.emitter.emit('notify', payload); } - onPollPreAuthCheck = async (): Promise => { - return true; - } + onPollPreAuthCheck = async (): Promise => true - onPollPostAuthCheck = async (): Promise => { - return true; - } + onPollPostAuthCheck = async (): Promise => true poll = async () => { if(!(await this.onPollPreAuthCheck())) { diff --git a/src/backend/sources/ChromecastSource.ts b/src/backend/sources/ChromecastSource.ts index 333fc9eb..620c25e6 100644 --- a/src/backend/sources/ChromecastSource.ts +++ b/src/backend/sources/ChromecastSource.ts @@ -246,11 +246,11 @@ export class ChromecastSource extends MemorySource { protected initializeClientPlatform = async (device: MdnsDeviceInfo): Promise<[CastClient, PersistentClient, PlatformType]> => { - let index = 0; + const index = 0; for(const address of device.addresses) { - let castClient = new CastClient; - let client: PersistentClient = new PersistentClient({host: address, client: castClient}); + const castClient = new CastClient; + const client: PersistentClient = new PersistentClient({host: address, client: castClient}); client.on('connect', () => this.handleCastClientEvent(device.name, 'connect')); client.on('reconnect', () => this.handleCastClientEvent(device.name, 'reconnect')); client.on('reconnecting', () => this.handleCastClientEvent(device.name, 'reconnecting')); @@ -351,7 +351,7 @@ export class ChromecastSource extends MemorySource { let storedApp = v.applications.get(a.transportId); if(!storedApp) { const appName = a.displayName; - let found = `Found Application '${appName}-${a.transportId.substring(0, 4)}'`; + const found = `Found Application '${appName}-${a.transportId.substring(0, 4)}'`; const appLowerName = appName.toLocaleLowerCase(); let filtered = false; let valid = true; @@ -489,7 +489,7 @@ export class ChromecastSource extends MemorySource { } getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}) => { - let plays: SourceData[] = []; + const plays: SourceData[] = []; try { await this.refreshApplications(); @@ -667,10 +667,11 @@ export class ChromecastSource extends MemorySource { let artists: string[] = [], albumArtists: string[] = [], - track: string = (title ?? songName) as string, - album: string = (albumNorm ?? albumName) as string, mediaType: string = 'unknown'; + const track: string = (title ?? songName) as string; + const album: string = (albumNorm ?? albumName) as string; + if(artist !== undefined) { artists = [artist as string]; } else if (artistName !== undefined) { diff --git a/src/backend/sources/DeezerSource.ts b/src/backend/sources/DeezerSource.ts index f65391f3..9aa941a0 100644 --- a/src/backend/sources/DeezerSource.ts +++ b/src/backend/sources/DeezerSource.ts @@ -42,7 +42,7 @@ export default class DeezerSource extends AbstractSource { this.logger.warn('Interval should be above 30 seconds...😬'); } - // @ts-ignore + // @ts-expect-error not correct structure this.config.data = { ...rest, interval, @@ -138,9 +138,7 @@ export default class DeezerSource extends AbstractSource { } } - getUpstreamRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => { - return this.getRecentlyPlayed(options); - } + getUpstreamRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => this.getRecentlyPlayed(options) getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}) => { const resp = await this.callApi(request.get(`${this.baseUrl}/user/me/history?limit=20`)); @@ -208,15 +206,14 @@ export default class DeezerSource extends AbstractSource { } = {}, response, } = e; - let msg = response !== undefined ? `API Call failed: Server Response => ${ssMessage}` : `API Call failed: ${message}`; + const msg = response !== undefined ? `API Call failed: Server Response => ${ssMessage}` : `API Call failed: ${message}`; const responseMeta = ssResp ?? text; this.logger.error(msg, {status, response: responseMeta}); throw e; } } - generatePassportStrategy = () => { - return new DeezerStrategy({ + generatePassportStrategy = () => new DeezerStrategy({ clientID: this.config.data.clientId, clientSecret: this.config.data.clientSecret, callbackURL: this.redirectUri, @@ -237,8 +234,7 @@ export default class DeezerSource extends AbstractSource { } return done(r); }); - }); - } + }) handleAuthCodeCallback = async (res: any) => { const {error, accessToken, id, displayName} = res; @@ -259,7 +255,5 @@ export default class DeezerSource extends AbstractSource { } } - protected getBackloggedPlays = async () => { - return await this.getRecentlyPlayed({formatted: true}); - } + protected getBackloggedPlays = async () => await this.getRecentlyPlayed({formatted: true}) } diff --git a/src/backend/sources/JRiverSource.ts b/src/backend/sources/JRiverSource.ts index b8de9ef7..39ea66cf 100644 --- a/src/backend/sources/JRiverSource.ts +++ b/src/backend/sources/JRiverSource.ts @@ -91,8 +91,8 @@ export class JRiverSource extends MemorySource { ZoneName, } = obj; - let artists = Artist === null || Artist === undefined ? [] : [Artist]; - let album = Album === null || Album === '' ? undefined : Album; + const artists = Artist === null || Artist === undefined ? [] : [Artist]; + const album = Album === null || Album === '' ? undefined : Album; const length = Number.parseInt(DurationMS.toString()) / 1000; return { diff --git a/src/backend/sources/JellyfinSource.ts b/src/backend/sources/JellyfinSource.ts index ad066037..6b4c4562 100644 --- a/src/backend/sources/JellyfinSource.ts +++ b/src/backend/sources/JellyfinSource.ts @@ -251,9 +251,7 @@ export default class JellyfinSource extends MemorySource { return true; } - getRecentlyPlayed = async (options = {}) => { - return this.getFlatRecentlyDiscoveredPlays(); - } + getRecentlyPlayed = async (options = {}) => this.getFlatRecentlyDiscoveredPlays() handle = async (playObj: PlayObject) => { if (!this.isValidEvent(playObj)) { @@ -366,7 +364,5 @@ export default class JellyfinSource extends MemorySource { } } - getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => { - return new JellyfinPlayerState(logger, id, opts); - } + getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => new JellyfinPlayerState(logger, id, opts) } diff --git a/src/backend/sources/KodiSource.ts b/src/backend/sources/KodiSource.ts index a7fc0353..b63441ab 100644 --- a/src/backend/sources/KodiSource.ts +++ b/src/backend/sources/KodiSource.ts @@ -53,7 +53,7 @@ export class KodiSource extends MemorySource { return []; } - let play = await this.client.getRecentlyPlayed(options); + const play = await this.client.getRecentlyPlayed(options); return this.processRecentPlays(play); } diff --git a/src/backend/sources/LastfmSource.ts b/src/backend/sources/LastfmSource.ts index a6bf01cb..d5e8daaa 100644 --- a/src/backend/sources/LastfmSource.ts +++ b/src/backend/sources/LastfmSource.ts @@ -152,7 +152,5 @@ export default class LastfmSource extends MemorySource { } } - protected getBackloggedPlays = async () => { - return await this.getRecentlyPlayed({formatted: true}); - } + protected getBackloggedPlays = async () => await this.getRecentlyPlayed({formatted: true}) } diff --git a/src/backend/sources/ListenbrainzSource.ts b/src/backend/sources/ListenbrainzSource.ts index 3946710c..94dc6528 100644 --- a/src/backend/sources/ListenbrainzSource.ts +++ b/src/backend/sources/ListenbrainzSource.ts @@ -78,7 +78,5 @@ export default class ListenbrainzSource extends MemorySource { } } - protected getBackloggedPlays = async () => { - return await this.getRecentlyPlayed({formatted: true}); - } + protected getBackloggedPlays = async () => await this.getRecentlyPlayed({formatted: true}) } diff --git a/src/backend/sources/MPRISSource.ts b/src/backend/sources/MPRISSource.ts index cd24d2f9..dff939b2 100644 --- a/src/backend/sources/MPRISSource.ts +++ b/src/backend/sources/MPRISSource.ts @@ -107,8 +107,8 @@ export class MPRISSource extends MemorySource { } protected listNew = async () => { - let iface = await this.getDBus(); - let names = (await iface.ListNames())[0]; + const iface = await this.getDBus(); + const names = (await iface.ListNames())[0]; return names.filter((n) => n.includes('org.mpris.MediaPlayer2')) } @@ -123,7 +123,7 @@ export class MPRISSource extends MemorySource { for (const playerName of newList) { const plainPlayerName = playerName.replace('org.mpris.MediaPlayer2.', ''); try { - let props = await busNew.getInterface(playerName, MPRIS_PATH, MPRIS_IFACE); + const props = await busNew.getInterface(playerName, MPRIS_PATH, MPRIS_IFACE); // may not always have position available! can fallback to undefined for this let pos: number | undefined; try { @@ -181,9 +181,9 @@ export class MPRISSource extends MemorySource { } metadataToPlain = (metadataVariant): MPRISMetadata => { - let metadataPlain = {}; - for (let k of Object.keys(metadataVariant)) { - let value = metadataVariant[k]; + const metadataPlain = {}; + for (const k of Object.keys(metadataVariant)) { + const value = metadataVariant[k]; if (value === undefined || value === null) { //logging.warn(`ignoring a null metadata value for key ${k}`); continue; @@ -201,7 +201,7 @@ export class MPRISSource extends MemorySource { getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}) => { const infos = await this.getPlayersInfo(); - let plays: PlayObject[] = []; + const plays: PlayObject[] = []; for(const info of infos) { const lowerName = info.name.toLocaleLowerCase(); if(this.whitelist.length > 0) { diff --git a/src/backend/sources/MemorySource.ts b/src/backend/sources/MemorySource.ts index 374e735f..1dc2a252 100644 --- a/src/backend/sources/MemorySource.ts +++ b/src/backend/sources/MemorySource.ts @@ -70,7 +70,6 @@ export default class MemorySource extends AbstractSource { deadPlatformIds.push([player.platformIdStr, `Removed after being orphaned for ${dayjs.duration(player.stateIntervalOptions.orphanedInterval, 'seconds').asMinutes()} minutes`]); } else if (isStale) { const state = player.getApiState(); - // @ts-ignore const stateHash = objectHash.sha1(state); if(stateHash !== this.playerState.get(key)) { this.playerState.set(key, stateHash); @@ -97,9 +96,7 @@ export default class MemorySource extends AbstractSource { return record; } - getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => { - return new GenericPlayerState(logger, id, opts); - } + getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => new GenericPlayerState(logger, id, opts) setNewPlayer = (idStr: string, logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions = {}) => { this.players.set(idStr, this.getNewPlayer(this.logger, id, { @@ -173,7 +170,7 @@ export default class MemorySource extends AbstractSource { // wait to discover play until it is stale or current play has changed // so that our discovered track has an accurate "listenedFor" count if (candidate !== undefined && (playChanged || player.isUpdateStale())) { - let stPrefix = `${buildTrackString(candidate, {include: ['trackId', 'artist', 'track']})}`; + const stPrefix = `${buildTrackString(candidate, {include: ['trackId', 'artist', 'track']})}`; const thresholdResults = timePassesScrobbleThreshold(scrobbleThresholds, candidate.data.listenedFor, candidate.data.duration); if (thresholdResults.passes) { @@ -215,7 +212,6 @@ export default class MemorySource extends AbstractSource { player.logSummary(); } const apiState = player.getApiState(); - // @ts-ignore this.playerState.set(key, objectHash.sha1(apiState)) this.emitEvent('playerUpdate', apiState); } @@ -224,9 +220,7 @@ export default class MemorySource extends AbstractSource { return newStatefulPlays; } - recentlyPlayedTrackIsValid = (playObj: any) => { - return playObj.data.playDate.isBefore(dayjs().subtract(30, 's')); - } + recentlyPlayedTrackIsValid = (playObj: any) => playObj.data.playDate.isBefore(dayjs().subtract(30, 's')) protected getInterval(): number { /** @@ -270,7 +264,7 @@ export default class MemorySource extends AbstractSource { } } -function sortByPlayDate(a: ProgressAwarePlayObject, b: ProgressAwarePlayObject): number { +const sortByPlayDate = (a: ProgressAwarePlayObject, b: ProgressAwarePlayObject): number => { throw new Error("Function not implemented."); -} +}; diff --git a/src/backend/sources/MopidySource.ts b/src/backend/sources/MopidySource.ts index 11576f4c..486f01e6 100644 --- a/src/backend/sources/MopidySource.ts +++ b/src/backend/sources/MopidySource.ts @@ -57,7 +57,7 @@ export class MopidySource extends MemorySource { this.client = new Mopidy({ autoConnect: false, webSocketUrl: this.url.toString(), - // @ts-ignore + // @ts-expect-error logger satisfies but is missing types not used console: winston.loggers.get('noop') }); this.client.on('state:offline', () => { diff --git a/src/backend/sources/PlayerState/AbstractPlayerState.ts b/src/backend/sources/PlayerState/AbstractPlayerState.ts index 8736f340..3fb936ed 100644 --- a/src/backend/sources/PlayerState/AbstractPlayerState.ts +++ b/src/backend/sources/PlayerState/AbstractPlayerState.ts @@ -205,7 +205,7 @@ export abstract class AbstractPlayerState { public getPlayedObject(completed: boolean = false): PlayObject | undefined { if(this.currentPlay !== undefined) { - let ranges = [...this.listenRanges]; + const ranges = [...this.listenRanges]; if (this.currentListenRange !== undefined) { ranges.push(this.currentListenRange); } @@ -228,7 +228,7 @@ export abstract class AbstractPlayerState { public getListenDuration(): Second{ let listenDur: number = 0; - let ranges = [...this.listenRanges]; + const ranges = [...this.listenRanges]; if (this.currentListenRange !== undefined) { ranges.push(this.currentListenRange); } @@ -375,7 +375,7 @@ export abstract class AbstractPlayerState { } public textSummary() { - let parts = ['']; + const parts = ['']; let play: string; if (this.currentPlay !== undefined) { parts.push(`${buildTrackString(this.currentPlay, {include: ['trackId', 'artist', 'track']})} @ ${this.playFirstSeenAt.toISOString()}`); diff --git a/src/backend/sources/PlexSource.ts b/src/backend/sources/PlexSource.ts index 294ffc07..458788db 100644 --- a/src/backend/sources/PlexSource.ts +++ b/src/backend/sources/PlexSource.ts @@ -91,8 +91,7 @@ export default class PlexSource extends AbstractSource { librarySectionTitle: library, // plex returns the track artist as originalTitle (when there is an album artist) // otherwise this is undefined - // @ts-expect-error - originalTitle: trackArtist + originalTitle: trackArtist = undefined } = {}, Server: { // @ts-expect-error TS(2525): Initializer provides no value for this binding ele... Remove this comment to see the full error message @@ -104,8 +103,8 @@ export default class PlexSource extends AbstractSource { } } = obj; - let artists: string[] = []; - let albumArtists: string[] = []; + const artists: string[] = []; + const albumArtists: string[] = []; if(trackArtist !== undefined) { artists.push(trackArtist); albumArtists.push(artist); @@ -241,13 +240,9 @@ export const plexRequestMiddle = () => { const form = formidable({ allowEmptyFiles: true, multiples: true, - // issue with typings https://github.com/node-formidable/formidable/issues/821 - // @ts-ignore - fileWriteStreamHandler: (file: any) => { - return concatStream((data: any) => { + fileWriteStreamHandler: (file: any) => concatStream((data: any) => { file.buffer = data; - }); - } + }) }); form.on('progress', (received: any, expected: any) => { plexLog.debug(`Received ${received} bytes of expected ${expected}`); diff --git a/src/backend/sources/ScrobbleSources.ts b/src/backend/sources/ScrobbleSources.ts index e643a8ac..84075278 100644 --- a/src/backend/sources/ScrobbleSources.ts +++ b/src/backend/sources/ScrobbleSources.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-case-declarations */ import { mergeArr, parseBool, readJson, validateJson } from "../utils.js"; import SpotifySource from "./SpotifySource.js"; import PlexSource from "./PlexSource.js"; @@ -61,22 +62,16 @@ export default class ScrobbleSources { this.logger = winston.loggers.get('app').child({labels: ['Sources']}, mergeArr); } - getByName = (name: any) => { - return this.sources.find(x => x.name === name); - } + getByName = (name: any) => this.sources.find(x => x.name === name) - getByType = (type: any) => { - return this.sources.filter(x => x.type === type); - } + getByType = (type: any) => this.sources.filter(x => x.type === type) - getByNameAndType = (name: string, type: SourceType) => { - return this.sources.find(x => x.name === name && x.type === type); - } + getByNameAndType = (name: string, type: SourceType) => this.sources.find(x => x.name === name && x.type === type) async getStatusSummary(type?: string, name?: string): Promise<[boolean, string[]]> { let sources: AbstractSource[] let sourcesReady = true; - let messages: string[] = []; + const messages: string[] = []; if(type !== undefined) { sources = this.getByType(type); @@ -101,7 +96,7 @@ export default class ScrobbleSources { } buildSourcesFromConfig = async (additionalConfigs: ParsedConfig[] = []) => { - let configs: ParsedConfig[] = additionalConfigs; + const configs: ParsedConfig[] = additionalConfigs; let configFile; try { @@ -138,7 +133,7 @@ export default class ScrobbleSources { } } - for (let sourceType of sourceTypes) { + for (const sourceType of sourceTypes) { let defaultConfigureAs = 'source'; // env builder for single user mode switch (sourceType) { @@ -376,7 +371,7 @@ export default class ScrobbleSources { try { const validConfig = validateJson(rawConf, sourceSchema, this.logger); - // @ts-ignore + // @ts-expect-error will eventually have all info (lazy) const parsedConfig: ParsedConfig = { ...rawConf, source: `${sourceType}.json`, diff --git a/src/backend/sources/SpotifySource.ts b/src/backend/sources/SpotifySource.ts index f6dcc84d..dd4098e5 100644 --- a/src/backend/sources/SpotifySource.ts +++ b/src/backend/sources/SpotifySource.ts @@ -233,7 +233,7 @@ export default class SpotifySource extends MemorySource { } if (validationErrors.length !== 0) { - this.logger.warn(`Configuration was not valid:\*${validationErrors.join('\n')}`); + this.logger.warn(`Configuration was not valid: *${validationErrors.join('\n')}`); throw new Error('Failed to initialize a Spotify source'); } @@ -284,9 +284,7 @@ export default class SpotifySource extends MemorySource { } } - createAuthUrl = () => { - return this.spotifyApi.createAuthorizeURL(scopes, this.name); - } + createAuthUrl = () => this.spotifyApi.createAuthorizeURL(scopes, this.name) handleAuthCodeCallback = async ({ error, @@ -508,39 +506,17 @@ export default class SpotifySource extends MemorySource { return true; } - protected getBackloggedPlays = async () => { - return await this.getPlayHistory({formatted: true}); - } + protected getBackloggedPlays = async () => await this.getPlayHistory({formatted: true}) } -const asPlayHistoryObject = (obj: object): obj is PlayHistoryObject => { - return 'played_at' in obj; -} +const asPlayHistoryObject = (obj: object): obj is PlayHistoryObject => 'played_at' in obj -const asCurrentlyPlayingObject = (obj: object): obj is CurrentlyPlayingObject => { - return 'is_playing' in obj; -} +const asCurrentlyPlayingObject = (obj: object): obj is CurrentlyPlayingObject => 'is_playing' in obj -const hasApiPermissionError = (e: Error): boolean => { - return findCauseByFunc(e, (err) => { - return err.message.includes('Permissions missing'); - }) !== undefined; -} +const hasApiPermissionError = (e: Error): boolean => findCauseByFunc(e, (err) => err.message.includes('Permissions missing')) !== undefined -const hasApiAuthError = (e: Error): boolean => { - return findCauseByFunc(e, (err) => { - return err.message.includes('An authentication error occurred'); - }) !== undefined; -} +const hasApiAuthError = (e: Error): boolean => findCauseByFunc(e, (err) => err.message.includes('An authentication error occurred')) !== undefined -const hasApiTimeoutError = (e: Error): boolean => { - return findCauseByFunc(e, (err) => { - return err.message.includes('A timeout occurred'); - }) !== undefined; -} +const hasApiTimeoutError = (e: Error): boolean => findCauseByFunc(e, (err) => err.message.includes('A timeout occurred')) !== undefined -const hasApiError = (e: Error): boolean => { - return findCauseByFunc(e, (err) => { - return err.message.includes('while communicating with Spotify\'s Web API.'); - }) !== undefined; -} +const hasApiError = (e: Error): boolean => findCauseByFunc(e, (err) => err.message.includes('while communicating with Spotify\'s Web API.')) !== undefined diff --git a/src/backend/sources/SubsonicSource.ts b/src/backend/sources/SubsonicSource.ts index 2e90b8d6..b08f1e19 100644 --- a/src/backend/sources/SubsonicSource.ts +++ b/src/backend/sources/SubsonicSource.ts @@ -88,7 +88,7 @@ export class SubsonicSource extends MemorySource { //queryOpts.p = password; queryOpts.p = `enc:${Buffer.from(password).toString('hex')}` } else { - const salt = await crypto.randomBytes(10).toString('hex'); + const salt = crypto.randomBytes(10).toString('hex'); const hash = crypto.createHash('md5').update(`${password}${salt}`).digest('hex') queryOpts.t = hash; queryOpts.s = salt; @@ -152,7 +152,7 @@ export class SubsonicSource extends MemorySource { } } - // @ts-ignore + // @ts-expect-error it is assignable to T idk return ssResp; } catch (e) { if(e instanceof UpstreamError) { @@ -247,14 +247,12 @@ export class SubsonicSource extends MemorySource { } } -export const getSubsonicResponseFromError = (error: unknown): UpstreamError => { - return findCauseByFunc(error, (err) => { +export const getSubsonicResponseFromError = (error: unknown): UpstreamError => findCauseByFunc(error, (err) => { if(err instanceof UpstreamError && err.response !== undefined) { return getSubsonicResponse(err.response) !== undefined; } return false; - }) as UpstreamError | undefined; -} + }) as UpstreamError | undefined export const parseApiResponseErrorToThrowable = (resp: SubsonicResponse) => { const { diff --git a/src/backend/sources/TautulliSource.ts b/src/backend/sources/TautulliSource.ts index 25ea38cb..ec2ffedb 100644 --- a/src/backend/sources/TautulliSource.ts +++ b/src/backend/sources/TautulliSource.ts @@ -40,19 +40,14 @@ export default class TautulliSource extends PlexSource { player, } = {} } = obj; - let artists: string[] = []; - let albumArtists: string[] = []; + const artists: string[] = []; + const albumArtists: string[] = []; if (track_artist !== undefined && track_artist !== artist_name) { artists.push(track_artist); albumArtists.push(artist_name); } else { artists.push(artist_name); } - if(action === undefined) { - //TODO why does TS think logger doesn't exist? - // @ts-ignore - this.logger.warn(`Payload did contain property 'action', assuming it should be 'watched'`); - } return { data: { artists, diff --git a/src/backend/sources/WebScrobblerSource.ts b/src/backend/sources/WebScrobblerSource.ts index 0e7d229c..c22d39ea 100644 --- a/src/backend/sources/WebScrobblerSource.ts +++ b/src/backend/sources/WebScrobblerSource.ts @@ -137,9 +137,7 @@ export class WebScrobblerSource extends MemorySource { } } - getRecentlyPlayed = async (options = {}) => { - return this.getFlatRecentlyDiscoveredPlays(); - } + getRecentlyPlayed = async (options = {}) => this.getFlatRecentlyDiscoveredPlays() isValidScrobble = (playObj: PlayObject) => { if (playObj.meta?.scrobbleAllowed === false) { diff --git a/src/backend/sources/YTMusicSource.ts b/src/backend/sources/YTMusicSource.ts index 79481a8e..0a44f16b 100644 --- a/src/backend/sources/YTMusicSource.ts +++ b/src/backend/sources/YTMusicSource.ts @@ -1,12 +1,9 @@ import YouTubeMusic from "youtube-music-ts-api"; - import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; -// @ts-ignore import {IYouTubeMusicAuthenticated} from "youtube-music-ts-api/interfaces-primary"; import dayjs from "dayjs"; import { parseDurationFromTimestamp, playObjDataMatch } from "../utils.js"; -// @ts-ignore import {IPlaylistDetail, ITrackDetail} from "youtube-music-ts-api/interfaces-supplementary"; import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; import EventEmitter from "events"; @@ -71,15 +68,13 @@ export default class YTMusicSource extends AbstractSource { } } - recentlyPlayedTrackIsValid = (playObj: PlayObject) => { - return playObj.meta.newFromSource; - } + recentlyPlayedTrackIsValid = (playObj: PlayObject) => playObj.meta.newFromSource api = async (): Promise => { if(this.apiInstance !== undefined) { return this.apiInstance; } - // @ts-ignore + // @ts-expect-error default does exist const ytm = new YouTubeMusic.default() as YouTubeMusic; try { this.apiInstance = await ytm.authenticate(this.config.data.cookie, this.config.data.authUser); @@ -152,8 +147,7 @@ export default class YTMusicSource extends AbstractSource { } if(newPlays.length > 0) { - newPlays = newPlays.map((x) => { - return { + newPlays = newPlays.map((x) => ({ data: { ...x.data, playDate: dayjs().startOf('minute') @@ -162,8 +156,7 @@ export default class YTMusicSource extends AbstractSource { ...x.meta, newFromSource: true } - } - }); + })); this.recentlyPlayed = newPlays.concat(this.recentlyPlayed).slice(0, 20); } } diff --git a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts index 1c565214..ef12b2de 100644 --- a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts +++ b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts @@ -16,7 +16,7 @@ export class TautulliNotifier extends IngressNotifier { if(!this.seenServers.includes(playObj.meta.server)) { this.seenServers.push(playObj.meta.server); - let msg = [`Received data from server ${playObj.meta.server} for the first time.`]; + const msg = [`Received data from server ${playObj.meta.server} for the first time.`]; if(req.body === undefined) { msg.push('WARNING: Payload was empty.'); } diff --git a/src/backend/tests/listenbrainz/listenbrainz.test.ts b/src/backend/tests/listenbrainz/listenbrainz.test.ts index 643e2be3..1e0d4ded 100644 --- a/src/backend/tests/listenbrainz/listenbrainz.test.ts +++ b/src/backend/tests/listenbrainz/listenbrainz.test.ts @@ -130,7 +130,7 @@ describe('Listenbrainz Response Behavior', function() { playDate: dayjs(), meta: { brainz: { - // @ts-expect-error + // @ts-expect-error wrong on purpose artist: 'fad8967c-a327-4af5-a64a-d4de66ece652;100846a7-06f6-4129-97ce-4409b9a9a311', album: '2eb6a8fb-14f6-436e-9bdf-2f9d0d8cbae0', track: '677862e0-3603-4120-8c44-ee9a70893647', diff --git a/src/backend/tests/utils/interfaces.ts b/src/backend/tests/utils/interfaces.ts index 90791a1c..d0cec580 100644 --- a/src/backend/tests/utils/interfaces.ts +++ b/src/backend/tests/utils/interfaces.ts @@ -1,5 +1,5 @@ export interface ExpectedResults { artists: string[] track: string - album?: String + album?: string } diff --git a/src/backend/utils.ts b/src/backend/utils.ts index 85b9e1f4..a713408e 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -1,5 +1,5 @@ import {accessSync, constants, promises} from "fs"; -import dayjs from 'dayjs'; +import dayjs, {Dayjs} from 'dayjs'; import utc from 'dayjs/plugin/utc.js'; import {Logger} from '@foxxmd/winston'; import JSON5 from 'json5'; @@ -135,8 +135,8 @@ export const sortByNewestPlayDate = (a: PlayObject, b: PlayObject) => { }; export const setIntersection = (setA: any, setB: any) => { - let _intersection = new Set() - for (let elem of setB) { + const _intersection = new Set() + for (const elem of setB) { if (setA.has(elem)) { _intersection.add(elem) } @@ -252,12 +252,11 @@ export const parseRetryAfterSecsFromObj = (err: any) => { } // first try to parse as float - let retryAfter = Number.parseFloat(raVal); + let retryAfter: number | Dayjs = Number.parseFloat(raVal); if (!isNaN(retryAfter)) { return retryAfter; // got a number! } // try to parse as date - // @ts-ignore retryAfter = dayjs(retryAfter); if (!dayjs.isDayjs(retryAfter)) { return undefined; // could not parse string if not in ISO 8601 format @@ -278,7 +277,7 @@ export const spreadDelay = (retries: any, multiplier: any) => { return []; } let r; - let s = []; + const s = []; for(r = 0; r < retries; r++) { s.push(((r+1) * multiplier) * 1000); } @@ -286,7 +285,7 @@ export const spreadDelay = (retries: any, multiplier: any) => { } export const removeUndefinedKeys = >(obj: T): T | undefined => { - let newObj: any = {}; + const newObj: any = {}; Object.keys(obj).forEach((key) => { if(Array.isArray(obj[key])) { newObj[key] = obj[key]; @@ -366,7 +365,7 @@ export const validateJson = (config: object, schema: Schema, logger: Logger): logger.error('Json config was not valid. Please use schema to check validity.', {leaf: 'Config'}); if (Array.isArray(ajv.errors)) { for (const err of ajv.errors) { - let parts = [ + const parts = [ `At: ${err.instancePath}`, ]; let data; @@ -379,9 +378,7 @@ export const validateJson = (config: object, schema: Schema, logger: Logger): parts.push(`Data: ${data}`); } let suffix = ''; - // @ts-ignore if (err.params.allowedValues !== undefined) { - // @ts-ignore suffix = err.params.allowedValues.join(', '); suffix = ` [${suffix}]`; } @@ -727,7 +724,7 @@ export const durationToHuman = (dur: Duration): string => { return parts.join(' '); } export const getAddress = (host = '0.0.0.0', logger?: Logger): { v4?: string, v6?: string, host: string } => { - const local = host = '0.0.0.0' || host === '::' ? 'localhost' : host; + const local = host === '0.0.0.0' || host === '::' ? 'localhost' : host; let v4: string, v6: string; try { diff --git a/src/backend/utils/MDNSUtils.ts b/src/backend/utils/MDNSUtils.ts index 053d37a0..4f05e73a 100644 --- a/src/backend/utils/MDNSUtils.ts +++ b/src/backend/utils/MDNSUtils.ts @@ -39,7 +39,7 @@ export const discoveryAvahi = async (service: string, options?: DiscoveryOptions maybeLogger.debug(`Starting mDNS discovery with Avahi => Listening for ${(duration / 1000).toFixed(2)}s`); let anyDiscovered = false; - let services = new Map(); + const services = new Map(); const triggerDiscovery = () => { for(const [k,v] of services.entries()) { @@ -114,7 +114,7 @@ export const discoveryNative = async (service: string, options?: DiscoveryOption maybeLogger.debug(`Starting mDNS discovery => Listening for ${(duration / 1000).toFixed(2)}s`); if (sanity) { - let services: ServiceType[] = []; + const services: ServiceType[] = []; const testBrowser = new Browser(ServiceType.all()) .on('serviceUp', (service: ServiceType) => { services.push(service) diff --git a/src/backend/utils/StringUtils.ts b/src/backend/utils/StringUtils.ts index e88b005f..bb468a98 100644 --- a/src/backend/utils/StringUtils.ts +++ b/src/backend/utils/StringUtils.ts @@ -7,19 +7,17 @@ import {strategies} from '@foxxmd/string-sameness'; const {levenStrategy, diceStrategy} = strategies; // cant use [^\w\s] because this also catches non-english characters -export const SYMBOLS_WHITESPACE_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\\[\]\/\s]/g); -export const SYMBOLS_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\\[\]\/]/g); +export const SYMBOLS_WHITESPACE_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\[\]/\s]/g); +export const SYMBOLS_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\[\]/]/g); export const MULTI_WHITESPACE_REGEX = new RegExp(/\s{2,}/g); -export const uniqueNormalizedStrArr = (arr: string[]): string[] => { - return arr.reduce((acc: string[], curr) => { +export const uniqueNormalizedStrArr = (arr: string[]): string[] => arr.reduce((acc: string[], curr) => { const normalizedCurr = normalizeStr(curr) if (!acc.some(x => normalizeStr(x) === normalizedCurr)) { return acc.concat(curr); } return acc; - }, []); -} + }, []) // https://stackoverflow.com/a/37511463/1469797 export const normalizeStr = (str: string, options?: {keepSingleWhitespace?: boolean}): string => { const {keepSingleWhitespace = false} = options || {}; @@ -46,7 +44,7 @@ export interface PlayCredits { * * * */ -export const SECONDARY_CAPTURED_REGEX = new RegExp(/[(\[]\s*(?ft\.?\W|feat\.?\W|featuring|vs\.?\W)\s*(?.*)[)\]](?.*)/i); +export const SECONDARY_CAPTURED_REGEX = new RegExp(/[([]\s*(?ft\.?\W|feat\.?\W|featuring|vs\.?\W)\s*(?.*)[)\]](?.*)/i); /** @@ -63,7 +61,7 @@ export const SECONDARY_CAPTURED_REGEX = new RegExp(/[(\[]\s*(?ft\.?\W|fe * !!!! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ******* * * */ -export const SECONDARY_FREE_REGEX = new RegExp(/^\s*(?ft\.?\W|feat\.?\W|featuring|vs\.?\W)\s*(?(?:.+?(?= - |\s*[(\[]))|(?:.*))(?.*)/i); +export const SECONDARY_FREE_REGEX = new RegExp(/^\s*(?ft\.?\W|feat\.?\W|featuring|vs\.?\W)\s*(?(?:.+?(?= - |\s*[([]))|(?:.*))(?.*)/i); const SECONDARY_REGEX_STRATS: RegExp[] = [SECONDARY_CAPTURED_REGEX, SECONDARY_FREE_REGEX]; @@ -78,7 +76,7 @@ const SECONDARY_REGEX_STRATS: RegExp[] = [SECONDARY_CAPTURED_REGEX, SECONDARY_FR * ^^^^^^^^^^^^^^********************************************** * * */ -export const PRIMARY_SECONDARY_SECTIONS_REGEX = new RegExp(/^(?.+?)(?(?:[(\[]?(?:\Wft\.?|\Wfeat\.?|featuring|\Wvs\.)).*)/i); +export const PRIMARY_SECONDARY_SECTIONS_REGEX = new RegExp(/^(?.+?)(?(?:[([]?(?:\Wft\.?|\Wfeat\.?|featuring|\Wvs\.)).*)/i); /** * For matching the most common track/artist pattern that has a joiner @@ -184,9 +182,7 @@ export const parseStringList = (str: string, delimiters: string[] = [',', '&', ' return explodedStrings.flat(1); }, [str]).map(x => x.trim()); } -export const containsDelimiters = (str: string) => { - return null !== str.match(/[,&\/\\]+/i); -} +export const containsDelimiters = (str: string) => null !== str.match(/[,&/\\]+/i) export const findDelimiters = (str: string) => { const found: string[] = []; for (const d of DELIMITERS) { diff --git a/src/backend/utils/TimeUtils.ts b/src/backend/utils/TimeUtils.ts index 11d29e70..5857e4b3 100644 --- a/src/backend/utils/TimeUtils.ts +++ b/src/backend/utils/TimeUtils.ts @@ -106,10 +106,10 @@ export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: P const referenceDuration = newDuration ?? existingDuration; const referenceListenedFor = newListenedFor ?? existingListenedFor; - let playDiffThreshold = diffThreshold; + const playDiffThreshold = diffThreshold; // check if existing play time is same as new play date - let scrobblePlayDiff = Math.abs(existingTsSOCDate.unix() - candidateTsSOCDate.unix()); + const scrobblePlayDiff = Math.abs(existingTsSOCDate.unix() - candidateTsSOCDate.unix()); result.date = { threshold: diffThreshold, diff: scrobblePlayDiff @@ -161,11 +161,13 @@ export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: P } export const timePassesScrobbleThreshold = (thresholds: ScrobbleThresholds, secondsTracked: number, playDuration?: number): ScrobbleThresholdResult => { let durationPasses = undefined, - durationThreshold: number | null = thresholds.duration ?? DEFAULT_SCROBBLE_DURATION_THRESHOLD, percentPasses = undefined, - percentThreshold: number | null = thresholds.percent ?? DEFAULT_SCROBBLE_PERCENT_THRESHOLD, percent: number | undefined; + const durationThreshold: number | null = thresholds.duration ?? DEFAULT_SCROBBLE_DURATION_THRESHOLD, + percentThreshold: number | null = thresholds.percent ?? DEFAULT_SCROBBLE_PERCENT_THRESHOLD; + + if (percentThreshold !== null && playDuration !== undefined && playDuration !== 0) { percent = Math.round(((secondsTracked / playDuration) * 100)); percentPasses = percent >= percentThreshold; -- 2.51.2 From d3a70bf2c198c0b30231b0c80e5197484b932156 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 19 Feb 2024 13:24:53 -0500 Subject: [PATCH 21/34] fix: Add comment for future typed linting --- eslint.config.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 7b649147..3488bbc0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,11 +5,19 @@ import tsEslint from 'typescript-eslint'; import arrow from 'eslint-plugin-prefer-arrow-functions'; export default tsEslint.config( + eslint.configs.recommended, + ...tsEslint.configs.recommended, +// use to enable typed linting (way more errors) https://typescript-eslint.io/linting/typed-linting +/* ...tsEslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { + project: true, + tsconfigDirName: import.meta.dirname, + }, + }, + },*/ { - extends: [ - eslint.configs.recommended, - ...tsEslint.configs.recommended, - ], files: ['src/backend/**/*.ts'], ignores: ['eslint.config.js'], plugins: { -- 2.51.2 From af24e0ffbea289ceeb4b8e32e79ae188c997ebb5 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 19 Feb 2024 15:09:55 -0500 Subject: [PATCH 22/34] chore(docker): Remove unused dependencies --- Dockerfile | 13 +++---------- debian.Dockerfile | 4 ---- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7895c227..48fc92d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,14 +5,11 @@ ENV TZ=Etc/GMT RUN \ echo "**** install build packages ****" && \ apk add --no-cache \ - alpine-base \ avahi \ avahi-tools \ - git \ nodejs \ npm \ - #yarn \ - openssh && \ + && \ echo "**** cleanup ****" && \ rm -rf \ /root/.cache \ @@ -26,9 +23,6 @@ ENV CONFIG_DIR=$data_dir COPY docker/root/ / -RUN npm install -g patch-package \ - && chown -R root:root /usr/local/lib/node_modules/patch-package - WORKDIR /app FROM base as build @@ -41,8 +35,7 @@ FROM base as build COPY --chown=abc:abc package*.json tsconfig.json ./ COPY --chown=abc:abc patches ./patches - -RUN npm install \ +RUN npm ci \ && chown -R root:root node_modules #RUN yarn install @@ -75,7 +68,7 @@ ENV IS_DOCKER=true # && rm -rf node_modules/ts-node \ # && rm -rf node_modules/typescript -RUN npm install --omit=dev \ +RUN npm ci --omit=dev \ && npm cache clean --force \ && chown -R abc:abc node_modules \ && rm -rf node_modules/@types diff --git a/debian.Dockerfile b/debian.Dockerfile index 8ca453cd..c41ba678 100644 --- a/debian.Dockerfile +++ b/debian.Dockerfile @@ -57,10 +57,6 @@ ENV CONFIG_DIR=$data_dir COPY docker/root / -RUN npm install -g \ - patch-package \ - && chown -R root:root /usr/lib/node_modules/patch-package - WORKDIR /app FROM base as build -- 2.51.2 From 8a43f0ded54bc6f4016a545599fa44341cbdbc37 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 19 Feb 2024 15:32:24 -0500 Subject: [PATCH 23/34] test arm64 building for debian docker image using updated node version #126 Using latest LTS (20) seems to have fixed TLS timeout/connection reset issue seen when trying to `npm ci` on LTS 18 --- .github/workflows/publishImage.yml | 2 +- debian.Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publishImage.yml b/.github/workflows/publishImage.yml index 4e12182c..044a28a9 100644 --- a/.github/workflows/publishImage.yml +++ b/.github/workflows/publishImage.yml @@ -45,7 +45,7 @@ jobs: suffix: '-debian' # can't build arm64 due to a TLS issue when running npm install?? # https://github.com/FoxxMD/multi-scrobbler/issues/126 - platforms: 'linux/amd64' + platforms: 'linux/amd64,linux/arm64' # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token permissions: packages: write diff --git a/debian.Dockerfile b/debian.Dockerfile index c41ba678..c294fb3c 100644 --- a/debian.Dockerfile +++ b/debian.Dockerfile @@ -2,7 +2,7 @@ FROM ghcr.io/linuxserver/baseimage-debian:bookworm as base ENV TZ=Etc/GMT -ENV NODE_VERSION 18.19.0 +ENV NODE_VERSION 20.11.1 # borrowing openssl header removal trick from offical docker-node # https://github.com/nodejs/docker-node/blob/main/18/bookworm-slim/Dockerfile#L8 -- 2.51.2 From 830d1a2243c99ce694695e4bc50f705567d7fb9f Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 6 Mar 2024 10:10:13 -0500 Subject: [PATCH 24/34] fix(flatpak): Add missing developer name https://github.com/flathub/io.github.foxxmd.multiscrobbler/pull/13#issuecomment-1949888692 --- flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml b/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml index c09e7144..84146fbd 100644 --- a/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml +++ b/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml @@ -11,6 +11,9 @@ https://foxxmd.github.io/multi-scrobbler/docs/configuration MIT MIT + + FoxxMD +

Track your music listening history from many different sources: -- 2.51.2 From 4675847fece6c9eea4c29dd35e3e726a5ec148a0 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 11 Mar 2024 16:14:45 -0400 Subject: [PATCH 25/34] fix(flatpak): Reduce summary length --- flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml b/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml index 84146fbd..4c603dc8 100644 --- a/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml +++ b/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml @@ -6,7 +6,7 @@ io.github.foxxmd.multiscrobbler.desktop multi-scrobbler -

Scrobbles music from many sources to many clients + Scrobbles music from many sources https://foxxmd.github.io/multi-scrobbler https://foxxmd.github.io/multi-scrobbler/docs/configuration MIT -- 2.51.2 From 2dde3840c6f17fabf23c0e59aa1c5342a22f4cac Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 20 Mar 2024 12:14:54 -0400 Subject: [PATCH 26/34] refactor(logging): Replace winston with @foxxmd/logging --- package-lock.json | 696 +++++++++--------- package.json | 8 +- patches/winston-daily-rotate-file+4.7.1.patch | 12 - patches/winston-null+2.0.0.patch | 10 - src/backend/common/infrastructure/Atomic.ts | 45 +- .../common/infrastructure/config/aioConfig.ts | 3 +- src/backend/common/logging.ts | 379 +--------- .../common/vendor/AbstractApiClient.ts | 16 +- src/backend/common/vendor/JRiverApiClient.ts | 4 +- src/backend/common/vendor/KodiApiClient.ts | 4 +- src/backend/common/vendor/LastfmApiClient.ts | 4 +- .../common/vendor/ListenbrainzApiClient.ts | 4 +- .../common/vendor/chromecast/interfaces.ts | 2 +- src/backend/index.ts | 30 +- src/backend/ioc.ts | 8 +- .../notifier/AbstractWebhookNotifier.ts | 4 +- src/backend/notifier/GotifyWebhookNotifier.ts | 2 +- src/backend/notifier/Notifiers.ts | 7 +- src/backend/notifier/NtfyWebhookNotifier.ts | 2 +- .../scrobblers/AbstractScrobbleClient.ts | 4 +- src/backend/scrobblers/LastfmScrobbler.ts | 2 +- .../scrobblers/ListenbrainzScrobbler.ts | 4 +- src/backend/scrobblers/MalojaScrobbler.ts | 2 +- src/backend/scrobblers/ScrobbleClients.ts | 13 +- src/backend/server/api.ts | 62 +- src/backend/server/auth.ts | 2 +- src/backend/server/deezerRoutes.ts | 2 +- src/backend/server/index.ts | 14 +- src/backend/server/jellyfinRoutes.ts | 4 +- src/backend/server/middleware.ts | 2 +- src/backend/server/plexRoutes.ts | 8 +- src/backend/server/tautulliRoutes.ts | 4 +- src/backend/server/webscrobblerRoutes.ts | 6 +- src/backend/sources/AbstractSource.ts | 4 +- src/backend/sources/ChromecastSource.ts | 7 +- src/backend/sources/JRiverSource.ts | 2 +- src/backend/sources/JellyfinSource.ts | 2 +- src/backend/sources/KodiSource.ts | 2 +- src/backend/sources/LastfmSource.ts | 2 +- src/backend/sources/ListenbrainzSource.ts | 2 +- src/backend/sources/MemorySource.ts | 2 +- src/backend/sources/MopidySource.ts | 4 +- .../PlayerState/AbstractPlayerState.ts | 4 +- .../sources/PlayerState/GenericPlayerState.ts | 2 +- .../PlayerState/JellyfinPlayerState.ts | 2 +- src/backend/sources/PlexSource.ts | 8 +- src/backend/sources/ScrobbleSources.ts | 6 +- .../ingressNotifiers/IngressNotifier.ts | 8 +- .../ingressNotifiers/JellyfinNotifier.ts | 5 +- .../sources/ingressNotifiers/PlexNotifier.ts | 5 +- .../ingressNotifiers/TautulliNotifier.ts | 5 +- .../ingressNotifiers/WebhookNotifier.ts | 5 +- src/backend/tasks/heartbeatClients.ts | 4 +- src/backend/tasks/heartbeatSources.ts | 4 +- src/backend/tests/jellyfin/jellyfin.test.ts | 4 +- .../tests/listenbrainz/listenbrainz.test.ts | 3 +- src/backend/tests/player/player.test.ts | 4 +- src/backend/tests/scrobbler/TestScrobbler.ts | 6 +- src/backend/utils.ts | 2 +- src/backend/utils/MDNSUtils.ts | 2 +- src/client/logs/LogLine.tsx | 18 +- src/client/logs/LogsSection.tsx | 13 +- src/client/logs/logDucks.ts | 5 +- src/client/logs/logsApi.ts | 6 +- src/client/utils/index.tsx | 2 +- src/core/Atomic.ts | 23 +- 66 files changed, 557 insertions(+), 985 deletions(-) delete mode 100644 patches/winston-daily-rotate-file+4.7.1.patch delete mode 100644 patches/winston-null+2.0.0.patch diff --git a/package-lock.json b/package-lock.json index 79a15605..dc94c1c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,8 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@foxxmd/chromecast-client": "^1.0.4", + "@foxxmd/logging": "^0.1.11", "@foxxmd/string-sameness": "^0.4.0", - "@foxxmd/winston": "3.3.31", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", "@reduxjs/toolkit": "^1.9.5", @@ -66,13 +66,8 @@ "superagent": "^8.0.9", "tailwindcss": "^3.3.3", "toad-scheduler": "^3.0.0", - "triple-beam": "^1.3.0", "tsx": "^4.7.0", "vite-express": "^0.13.0", - "winston-daily-rotate-file": "^4.5.0", - "winston-duplex": "0.1.3", - "winston-null": "^2.0.0", - "winston-transport": "^4.4.0", "xml2js": "0.5.0", "youtube-music-ts-api": "^1.7.0" }, @@ -105,7 +100,6 @@ "@types/react-window": "^1.8.5", "@types/spotify-web-api-node": "^5.0.7", "@types/superagent": "^4.1.16", - "@types/triple-beam": "^1.3.2", "@types/xml2js": "^0.4.11", "@vitejs/plugin-react": "^4.2.1", "chai": "^4.3.6", @@ -129,6 +123,47 @@ "npm": ">=9.1.0" } }, + "../foxxmd/logging": { + "name": "@foxxmd/logging", + "version": "0.1.11", + "extraneous": true, + "license": "MIT", + "dependencies": { + "pino": "^8.19.0", + "pino-abstract-transport": "^1.1.0", + "pino-pretty": "^11.0.0", + "pino-roll": "^1.0.1", + "pump": "^3.0.0" + }, + "devDependencies": { + "@types/chai": "^4.3.0", + "@types/chai-as-promised": "^7.1.5", + "@types/dateformat": "^5.0.2", + "@types/mocha": "^9.1.0", + "@types/node": "^18.0.0", + "@types/pump": "^1.1.3", + "chai": "^4.3.6", + "chai-as-promised": "^7.1.1", + "dateformat": "^5.0.3", + "mocha": "^10.2.0", + "p-event": "^6.0.0", + "sinon": "^17.0.1", + "sinon-chai": "^3.7.0", + "ts-essentials": "^9.4.1", + "tshy": "^1.7.0", + "tsx": "^4.7.1", + "typedoc": "^0.25.11", + "typedoc-plugin-inline-sources": "^1.0.2", + "typedoc-plugin-missing-exports": "^2.2.0", + "typedoc-plugin-replace-text": "^3.3.0", + "typescript": "^5.3.3", + "with-local-tmp-dir": "^5.1.1" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.3.0" + } + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -1010,14 +1045,6 @@ "statuses": "^2.0.1" } }, - "node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1040,16 +1067,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, "node_modules/@dbus-types/dbus": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@dbus-types/dbus/-/dbus-0.0.4.tgz", @@ -1608,6 +1625,22 @@ "zod-validation-error": "^2.1.0" } }, + "node_modules/@foxxmd/logging": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@foxxmd/logging/-/logging-0.1.11.tgz", + "integrity": "sha512-k0qdUFvtyzqlrCDxaXNJCjpKCzfa8jdspArhE/pXd5Lnv69jJvwNVFIwgbfm+Yhrr7rwY+y5/gxSpZKZc19reg==", + "dependencies": { + "pino": "^8.19.0", + "pino-abstract-transport": "^1.1.0", + "pino-pretty": "^11.0.0", + "pino-roll": "^1.0.1", + "pump": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.3.0" + } + }, "node_modules/@foxxmd/string-sameness": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@foxxmd/string-sameness/-/string-sameness-0.4.0.tgz", @@ -1621,26 +1654,6 @@ "npm": ">=9.3.0" } }, - "node_modules/@foxxmd/winston": { - "version": "3.3.31", - "resolved": "https://registry.npmjs.org/@foxxmd/winston/-/winston-3.3.31.tgz", - "integrity": "sha512-7Qz87xrYjYo9K8biVCYDFN7z2XTQEurlyVAcqGQBfko4CJCovTbBuNMXHQFiysF4zmB8w15thVR30wdpCXJHUA==", - "dependencies": { - "@dabh/diagnostics": "^2.0.2", - "async": "^3.1.0", - "is-stream": "^2.0.0", - "lodash.mergewith": "^4.6.2", - "logform": "^2.2.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" - }, - "engines": { - "node": ">= 6.4.0" - } - }, "node_modules/@homebridge/long": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/@homebridge/long/-/long-5.2.1.tgz", @@ -2877,11 +2890,6 @@ "@types/jest": "*" } }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" - }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -3244,6 +3252,17 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3510,11 +3529,6 @@ "node": ">=4" } }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3528,6 +3542,14 @@ "node": ">= 4.0.0" } }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.17", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", @@ -3621,7 +3643,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -4176,15 +4197,6 @@ "node": ">=6" } }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4201,44 +4213,10 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/color/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -4394,12 +4372,12 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, - "node_modules/cycle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", - "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "engines": { - "node": ">=0.4.0" + "node": "*" } }, "node_modules/dayjs": { @@ -4743,11 +4721,6 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -5121,6 +5094,22 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -5278,6 +5267,11 @@ "node": "> 0.1.90" } }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5310,6 +5304,14 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" + } + }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -5323,11 +5325,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -5364,14 +5361,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-stream-rotator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", - "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", - "dependencies": { - "moment": "^2.29.1" - } - }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5497,11 +5486,6 @@ "node": ">=0.4.0" } }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, "node_modules/follow-redirects": { "version": "1.15.5", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", @@ -6018,6 +6002,11 @@ "integrity": "sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==", "dev": true }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -6081,7 +6070,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -6261,11 +6249,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -6533,6 +6516,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "peer": true, "engines": { "node": ">=8" }, @@ -6933,6 +6918,14 @@ "jiti": "bin/jiti.js" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7170,11 +7163,6 @@ "uuid": "^8.3.2" } }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - }, "node_modules/lastfm-node-client": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/lastfm-node-client/-/lastfm-node-client-2.2.0.tgz", @@ -7250,11 +7238,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -7271,30 +7254,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/logform": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", - "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", - "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/logform/node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", - "engines": { - "node": ">=10" - } - }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -7600,14 +7559,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "engines": { - "node": "*" - } - }, "node_modules/mopidy": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/mopidy/-/mopidy-1.3.0.tgz", @@ -8296,6 +8247,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -8323,14 +8282,6 @@ "wrappy": "1" } }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "dependencies": { - "fn.name": "1.x.x" - } - }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -8778,6 +8729,157 @@ "node": ">=6" } }, + "node_modules/pino": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.19.0.tgz", + "integrity": "sha512-oswmokxkav9bADfJ2ifrvfHUwad6MLp73Uat0IkQWY3iAw5xTRoznXbXksZs8oaOUMpmhVWD+PZogNzllWpJaA==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.1.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", + "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.0.0.tgz", + "integrity": "sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-roll": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pino-roll/-/pino-roll-1.0.1.tgz", + "integrity": "sha512-9mMf0dn7KsqiY/Bs7gDRoBd5lsksSknPnbTEFKQVTeuGXi8RFuXS5UkLw4StIY/uG6PMrM7KJydB7ramoyVDzA==", + "dependencies": { + "sonic-boom": "^3.8.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + }, + "node_modules/pino/node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -9040,6 +9142,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-on-spawn": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", @@ -9053,6 +9163,11 @@ "node": ">=8" } }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -9166,6 +9281,11 @@ } ] }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -9356,6 +9476,14 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/recast": { "version": "0.20.5", "resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz", @@ -9698,6 +9826,11 @@ "loose-envify": "^1.1.0" } }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -9886,14 +10019,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -9947,6 +10072,14 @@ "node": ">=6" } }, + "node_modules/sonic-boom": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.0.tgz", + "integrity": "sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -10008,6 +10141,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/spotify-web-api-node": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/spotify-web-api-node/-/spotify-web-api-node-5.0.2.tgz", @@ -10023,14 +10164,6 @@ "dev": true, "peer": true }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "engines": { - "node": "*" - } - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -10146,7 +10279,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -10437,11 +10569,6 @@ "node": "*" } }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10467,6 +10594,14 @@ "node": ">=0.8" } }, + "node_modules/thread-stream": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", + "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -10536,14 +10671,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", - "engines": { - "node": ">= 14.0.0" - } - }, "node_modules/ts-api-utils": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", @@ -11194,141 +11321,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/winston": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", - "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", - "peer": true, - "dependencies": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.4.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-compat": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/winston-compat/-/winston-compat-0.1.5.tgz", - "integrity": "sha512-EPvPcHT604AV3Ji6d3+vX8ENKIml9VSxMRnPQ+cuK/FX6f3hvPP2hxyoeeCOCFvDrJEujalfcKWlWPvAnFyS9g==", - "dependencies": { - "cycle": "~1.0.3", - "logform": "^1.6.0", - "triple-beam": "^1.2.0" - }, - "engines": { - "node": ">= 6.4.0" - } - }, - "node_modules/winston-compat/node_modules/fecha": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", - "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" - }, - "node_modules/winston-compat/node_modules/logform": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-1.10.0.tgz", - "integrity": "sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg==", - "dependencies": { - "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", - "fecha": "^2.3.3", - "ms": "^2.1.1", - "triple-beam": "^1.2.0" - } - }, - "node_modules/winston-daily-rotate-file": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz", - "integrity": "sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==", - "dependencies": { - "file-stream-rotator": "^0.6.1", - "object-hash": "^2.0.1", - "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "winston": "^3" - } - }, - "node_modules/winston-daily-rotate-file/node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/winston-duplex": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/winston-duplex/-/winston-duplex-0.1.3.tgz", - "integrity": "sha512-zBMeRsuiVobqhpRQ1Jetk3i7cVR2Vm/VvWEq0pcnMb4abueW/WycomtKO1j94nLzi2+lgjZVrODSDgHWmRnPGA==", - "dependencies": { - "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/winston-null": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/winston-null/-/winston-null-2.0.0.tgz", - "integrity": "sha512-uS5tJB5OkLWOoc3I7/LsWUfTIa5Du38XSviHf/b0TINK659Np9368FJwTt15UoZQYUQVxLpM06lxk2dKET22Xw==", - "dependencies": { - "semver": "^5.6.0", - "winston-compat": "^0.1.4", - "winston-transport": "^4.2.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "winston": ">=2.0.0" - } - }, - "node_modules/winston-null/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/winston-transport": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", - "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", - "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston/node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", - "peer": true, - "engines": { - "node": ">=10" - } - }, "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", diff --git a/package.json b/package.json index bf0577a1..b9fe2b2e 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@foxxmd/chromecast-client": "^1.0.4", + "@foxxmd/logging": "^0.1.11", "@foxxmd/string-sameness": "^0.4.0", - "@foxxmd/winston": "3.3.31", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", "@reduxjs/toolkit": "^1.9.5", @@ -100,13 +100,8 @@ "superagent": "^8.0.9", "tailwindcss": "^3.3.3", "toad-scheduler": "^3.0.0", - "triple-beam": "^1.3.0", "tsx": "^4.7.0", "vite-express": "^0.13.0", - "winston-daily-rotate-file": "^4.5.0", - "winston-duplex": "0.1.3", - "winston-null": "^2.0.0", - "winston-transport": "^4.4.0", "xml2js": "0.5.0", "youtube-music-ts-api": "^1.7.0" }, @@ -139,7 +134,6 @@ "@types/react-window": "^1.8.5", "@types/spotify-web-api-node": "^5.0.7", "@types/superagent": "^4.1.16", - "@types/triple-beam": "^1.3.2", "@types/xml2js": "^0.4.11", "@vitejs/plugin-react": "^4.2.1", "chai": "^4.3.6", diff --git a/patches/winston-daily-rotate-file+4.7.1.patch b/patches/winston-daily-rotate-file+4.7.1.patch deleted file mode 100644 index 5d26eabe..00000000 --- a/patches/winston-daily-rotate-file+4.7.1.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/node_modules/winston-daily-rotate-file/index.js b/node_modules/winston-daily-rotate-file/index.js -index c818660..a8d3040 100644 ---- a/node_modules/winston-daily-rotate-file/index.js -+++ b/node_modules/winston-daily-rotate-file/index.js -@@ -1,6 +1,6 @@ - 'use strict'; - --var winston = require('winston'); -+var winston = require('@foxxmd/winston'); - var DailyRotateFile = require('./daily-rotate-file'); - - winston.transports.DailyRotateFile = DailyRotateFile; diff --git a/patches/winston-null+2.0.0.patch b/patches/winston-null+2.0.0.patch deleted file mode 100644 index 90be862c..00000000 --- a/patches/winston-null+2.0.0.patch +++ /dev/null @@ -1,10 +0,0 @@ -diff --git a/node_modules/winston-null/index.js b/node_modules/winston-null/index.js -index 1a8a4ff..5088d6c 100644 ---- a/node_modules/winston-null/index.js -+++ b/node_modules/winston-null/index.js -@@ -1,4 +1,4 @@ --const winston = require('winston'); -+const winston = require('@foxxmd/winston'); - const compat = require('winston-compat'); - const semver = require('semver'); - diff --git a/src/backend/common/infrastructure/Atomic.ts b/src/backend/common/infrastructure/Atomic.ts index 0909f857..1b36c804 100644 --- a/src/backend/common/infrastructure/Atomic.ts +++ b/src/backend/common/infrastructure/Atomic.ts @@ -1,10 +1,10 @@ import {Dayjs} from "dayjs"; import {FixedSizeList} from 'fixed-size-list'; -import {Logger} from '@foxxmd/winston'; +import {Logger} from '@foxxmd/logging'; import TupleMap from "../TupleMap.js"; import {Request, Response} from "express"; import {NextFunction, ParamsDictionary, Query} from "express-serve-static-core"; -import { LogLevel, logLevels, PlayMeta, PlayObject } from "../../../core/Atomic.js"; +import {PlayMeta, PlayObject} from "../../../core/Atomic.js"; export type SourceType = 'spotify' | 'plex' | 'tautulli' | 'subsonic' | 'jellyfin' | 'lastfm' | 'deezer' | 'ytmusic' | 'mpris' | 'mopidy' | 'listenbrainz' | 'jriver' | 'kodi' | 'webscrobbler' | 'chromecast'; export const sourceTypes: SourceType[] = ['spotify', 'plex', 'tautulli', 'subsonic', 'jellyfin', 'lastfm', 'deezer', 'ytmusic', 'mpris', 'mopidy', 'listenbrainz', 'jriver', 'kodi', 'webscrobbler', 'chromecast']; @@ -105,45 +105,6 @@ export interface RemoteIdentityParts { agent: string | undefined } -export interface LogConfig { - level?: string - file?: string | false - stream?: string - console?: string | false -} - -export interface LogOptions { - /** - * Specify the minimum log level for all log outputs without their own level specified. - * - * Defaults to env `LOG_LEVEL` or `info` if not specified. - * - * @default 'info' - * */ - level?: LogLevel - /** - * Specify the minimum log level to output to rotating files. If `false` no log files will be created. - * */ - file?: LogLevel | false - /** - * Specify the minimum log level streamed to the UI - * */ - stream?: LogLevel - /** - * Specify the minimum log level streamed to the console (or docker container) - * */ - console?: LogLevel | false -} - -export const asLogOptions = (obj: LogConfig = {}): obj is LogOptions => { - return Object.entries(obj).every(([key, val]) => { - if(key !== 'file') { - return val === undefined || logLevels.includes(val.toLocaleLowerCase()); - } - return val === undefined || val === false || logLevels.includes(val.toLocaleLowerCase()); - }); -} - // https://stackoverflow.com/questions/40510611/typescript-interface-require-one-of-two-properties-to-exist#comment116238286_49725198 export type RequireAtLeastOne = Omit & { [ P in R ] : Required> & Partial> }[R]; @@ -232,3 +193,5 @@ export interface MdnsDeviceInfo { type: string addresses: string[] } + +export type AbstractApiOptions = Record & { logger: Logger } diff --git a/src/backend/common/infrastructure/config/aioConfig.ts b/src/backend/common/infrastructure/config/aioConfig.ts index 5e8dd863..1eb73bd2 100644 --- a/src/backend/common/infrastructure/config/aioConfig.ts +++ b/src/backend/common/infrastructure/config/aioConfig.ts @@ -3,7 +3,8 @@ import { RequestRetryOptions } from "./common.js"; import { SourceAIOConfig } from "./source/sources.js"; import { ClientAIOConfig } from "./client/clients.js"; import { WebhookConfig } from "./health/webhooks.js"; -import { LogOptions } from "../Atomic.js"; +import {LogOptions} from "@foxxmd/logging"; + export interface SourceDefaults extends SourceRetryOptions { /** diff --git a/src/backend/common/logging.ts b/src/backend/common/logging.ts index 346840cd..ec13c566 100644 --- a/src/backend/common/logging.ts +++ b/src/backend/common/logging.ts @@ -1,367 +1,48 @@ import path from "path"; import { projectDir } from "./index.js"; -import * as winstonNs from '@foxxmd/winston'; -import winstonDef from '@foxxmd/winston'; -import {DuplexTransport} from "winston-duplex"; -import { asLogOptions, LogConfig, LogOptions } from "./infrastructure/Atomic.js"; import process from "process"; -import { fileOrDirectoryIsWriteable, mergeArr, parseBool } from "../utils.js"; -import {ErrorWithCause, stackWithCauses} from "pony-cause"; -import {NullTransport} from 'winston-null'; -import DailyRotateFile from 'winston-daily-rotate-file'; -import dayjs from "dayjs"; -import stringify from 'safe-stable-stringify'; -import {SPLAT, LEVEL, MESSAGE} from 'triple-beam'; -import { LogInfo, LogLevel } from "../../core/Atomic.js"; -import TransportStream from "winston-transport"; -import {format} from 'logform'; - -const {combine, printf, timestamp, label, splat, errors} = format; - -//const {transports} = winstonNew; -const {loggers, transports} = winstonDef; +import { + parseLogOptions, + loggerAppRolling, + LogOptions as FoxLogOptions, + Logger as FoxLogger, + childLogger, +} from '@foxxmd/logging'; +import {PassThrough, Transform} from "node:stream"; +import {buildLogger, buildDestinationStdout, buildDestinationJsonPrettyStream} from "@foxxmd/logging/factory"; export let logPath = path.resolve(projectDir, `./logs`); if (typeof process.env.CONFIG_DIR === 'string') { logPath = path.resolve(process.env.CONFIG_DIR, './logs'); } -loggers.add('noop', {transports: [new NullTransport()]}); - -export const getLogger = (config: LogConfig = {}, name = 'app'): winstonNs.Logger => { - - if (!loggers.has(name)) { - const errors: (Error | string)[] = []; - - let options: LogOptions = {}; - if (asLogOptions(config)) { - options = config; - } else { - errors.push(`Logging levels were not valid. Must be one of: 'error', 'warn', 'info', 'verbose', 'debug' -- 'file' may be false.`); - } - - const {level: configLevel} = options; - const defaultLevel = process.env.LOG_LEVEL || (parseBool(process.env.DEBUG_MODE) ? 'debug' : 'info'); - let consoleLevel: string | boolean = process.env.CONSOLE_LEVEL || 'debug'; - if(consoleLevel === 'false') { - consoleLevel = false; - } - let fileLevel: string | boolean = process.env.FILE_LEVEL || defaultLevel; - if(fileLevel === 'false') { - fileLevel = false; - } - const { - level = configLevel || defaultLevel, - file = configLevel || fileLevel, - stream = configLevel || 'debug', - console = configLevel || consoleLevel - } = options; - - const myTransports: TransportStream[] = [ - new DuplexTransport({ - stream: { - transform: (chunk, e, cb) => { - cb(null, chunk); - }, - objectMode: true, - }, - name: 'duplex', - level: stream, - dump: false, - }) - ]; - - if(console !== false) { - myTransports.push(new transports.Console({ - level: console, - })); - } - - if (file !== false) { - const rotateTransport = new DailyRotateFile({ - dirname: logPath, - createSymlink: true, - symlinkName: 'scrobble-current.log', - filename: 'scrobble-%DATE%.log', - datePattern: 'YYYY-MM-DD', - maxSize: '5m', - level: file, - }); - - try { - fileOrDirectoryIsWriteable(logPath); - myTransports.push(rotateTransport); - } catch (e: any) { - const msg = 'WILL NOT write logs to rotating file due to an error while trying to access the specified logging directory'; - errors.push(new ErrorWithCause(msg, {cause: e})); - } - } - - const loggerOptions: winstonNs.LoggerOptions = { - level: level, - format: labelledFormat(), - transports: myTransports, - }; - - loggers.add(name, loggerOptions); - - const logger = loggers.get(name); - if (errors.length > 0) { - for (const e of errors) { - logger.error(e); - } - } - return logger; - } - return loggers.get(name); -} - -const breakSymbol = '
'; -export const formatLogToHtml = (chunk: any) => { - const line = chunk.toString().replace('\n', breakSymbol) - .replace(/(debug)\s/gi, '$1 ') - .replace(/(warn)\s/gi, '$1 ') - .replace(/(info)\s/gi, '$1 ') - .replace(/(verbose)\s/gi, '$1 ') - .replace(/(error)\s/gi, '$1 ') - .trim(); - if(line.slice(-6) !== breakSymbol) { - return `${line}${breakSymbol}`; - } - return line; +export const initLogger = (): [FoxLogger, Transform] => { + const opts = parseLogOptions({file: false, console: 'debug'}) + const stream = new PassThrough({objectMode: true}); + const logger = buildLogger('debug', [ + buildDestinationStdout(opts.console), + buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: false}) + ]); + return [logger, stream]; } -const levelSymbol = Symbol.for('level'); -const s = splat(); -//const errorsFormat = errors({stack: true}); -const CWD = process.cwd(); - -const causeKeys = ['name', 'cause', 'showStopper'] - -export const defaultFormat = (defaultLabel = 'App') => printf(({ - label, - [levelSymbol]: levelSym, - level, - message, - labels = [defaultLabel], - leaf, - timestamp, - durationMs, - [SPLAT]: splatObj, - stack, - id, - cause, - showStopper, - ...rest - }) => { - const keys = Object.keys(rest); - const stringifyValue = keys.length > 0 && !keys.every(x => causeKeys.some(y => y == x)) ? stringify.default(rest) : ''; - let msg = message; - let stackMsg = ''; - if (stack !== undefined) { - const stackArr = stack.split('\n'); - const stackTop = stackArr[0]; - const cleanedStack = stackArr - .slice(1) // don't need actual error message since we are showing it as msg - .map((x: string) => x.replace(CWD, 'CWD')) // replace file location up to cwd for user privacy - .join('\n'); // rejoin with newline to preserve formatting - stackMsg = `\n${cleanedStack}`; - if (msg === undefined || msg === null || typeof message === 'object') { - msg = stackTop; - } else { - stackMsg = `\n${stackTop}${stackMsg}` - } - } - - const nodes = Array.isArray(labels) ? labels : [labels]; - if (leaf !== null && leaf !== undefined && !nodes.includes(leaf)) { - nodes.push(leaf); - } - const labelContent = `${nodes.map((x: string) => `[${x}]`).join(' ')}`; - - return `${timestamp} ${level.padEnd(8)}: ${labelContent} ${msg}${stringifyValue !== '' ? ` ${stringifyValue}` : ''}${stackMsg}`; -}); - -// https://knowyourmeme.com/memes/cereal-guy -// this number will never overflow -let seqId: number = 0; -export const labelledFormat = (labelName = 'App') => { - const l = label({label: labelName, message: false}); - return combine( - timestamp( - { - format: () => dayjs().local().format(), - } - ), - { - transform: (info, opts) => { - info.id = seqId; - seqId++; - return info; - } - }, - l, - s, - errorAwareFormat, - defaultFormat(labelName), - ); +export const appLogger = async (config?: FoxLogOptions): Promise<[FoxLogger, PassThrough]> => { + const stream = new PassThrough({objectMode: true}); + const opts = parseLogOptions(config) + const logger = await loggerAppRolling(config, { + logBaseDir: typeof process.env.CONFIG_DIR === 'string' ? process.env.CONFIG_DIR : undefined, + destinations: [ + buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: false}) + ] + }); + return [logger, stream]; } - -export const logLevels = { - error: 0, - warn: 1, - info: 2, - http: 3, - verbose: 4, - debug: 5, - trace: 5, - silly: 6 -}; - -export const LOG_LEVEL_REGEX: RegExp = /\s*(debug|warn|info|error|verbose)\s*:/i -export const isLogLineMinLevel = (log: string | LogInfo, minLevelText: LogLevel): boolean => { - const minLevel = logLevels[minLevelText]; - let level: number; - - if(typeof log === 'string') { - const lineLevelMatch = log.match(LOG_LEVEL_REGEX) - if (lineLevelMatch === null) { - return false; - } - level = logLevels[lineLevelMatch[1]]; - } else { - const lineLevelMatch = log.level; - level = logLevels[lineLevelMatch]; - } - return level <= minLevel; -} - -export const isLogLevelMinLevel = (levelStr: LogLevel, minLevelStr: LogLevel): boolean => logLevels[levelStr] <= logLevels[minLevelStr] - -const isProbablyError = (val: any, explicitErrorName?: string) => { - if(typeof val !== 'object' || val === null) { - return false; - } - const {name, stack} = val; - if(explicitErrorName !== undefined) { - if(name !== undefined && name.toLowerCase().includes(explicitErrorName)) { - return true; - } - if(stack !== undefined && stack.trim().toLowerCase().indexOf(explicitErrorName.toLowerCase()) === 0) { - return true; - } - return false; - } else if(stack !== undefined) { - return true; - } else if(name !== undefined && name.toLowerCase().includes('error')) { - return true; - } - - return false; -} - -const errorAwareFormat = { - transform: (einfo: any, {stack = true}: any = {}) => { - - // because winston logger.child() re-assigns its input to an object ALWAYS the object we recieve here will never actually be of type Error - const includeStack = stack && (!isProbablyError(einfo, 'simpleerror') && !isProbablyError(einfo.message, 'simpleerror')); - - if (!isProbablyError(einfo.message) && !isProbablyError(einfo)) { - return einfo; - } - - let info: any = {}; - - if (isProbablyError(einfo)) { - const tinfo = transformError(einfo); - info = Object.assign({}, tinfo, { - level: einfo.level, - [LEVEL]: einfo[LEVEL] || einfo.level, - message: tinfo.message, - - [MESSAGE]: tinfo[MESSAGE] || tinfo.message - }); - if(includeStack) { - // so we have to create a dummy error and re-assign all error properties from our info object to it so we can get a proper stack trace - const dummyErr = new ErrorWithCause(''); - const names = Object.getOwnPropertyNames(tinfo); - for(const k of names) { - // eslint-disable-next-line no-prototype-builtins - if(dummyErr.hasOwnProperty(k) || k === 'cause') { - dummyErr[k] = tinfo[k]; - } - } - info.stack = stackWithCauses(dummyErr); - } - } else { - const err = transformError(einfo.message); - info = Object.assign({}, einfo, err); - info.message = err.message; - info[MESSAGE] = err.message; - - if(includeStack) { - const dummyErr = new ErrorWithCause(''); - // Error properties are not enumerable - // https://stackoverflow.com/a/18278145/1469797 - const names = Object.getOwnPropertyNames(err); - for(const k of names) { - // eslint-disable-next-line no-prototype-builtins - if(dummyErr.hasOwnProperty(k) || k === 'cause') { - dummyErr[k] = err[k]; - } - } - info.stack = stackWithCauses(dummyErr); - } - } - - // remove redundant message from stack and make stack causes easier to read - if(info.stack !== undefined) { - let cleanedStack = info.stack.replace(info.message, ''); - cleanedStack = `${cleanedStack}`; - cleanedStack = cleanedStack.replaceAll('caused by:', '\ncaused by:'); - info.stack = cleanedStack; - } - - return info; - } -} - -export const transformError = (err: Error): any => _transformError(err, new Set()); - -const _transformError = (err: Error, seen: Set) => { - if (!err || !isProbablyError(err)) { - return ''; - } - if (seen.has(err)) { - return err; - } - - try { - - // @ts-expect-error type missing expected props - const mOpts = err.matchOptions ?? matchOptions; - - const cause = err.cause as unknown; - - if (cause !== undefined && cause instanceof Error) { - // @ts-expect-error type missing expected props - err.cause = _transformError(cause, seen, mOpts); - } - - return err; - } catch (e: any) { - // oops :( - // we're gonna swallow silently instead of reporting to avoid any infinite nesting and hopefully the original error looks funny enough to provide clues as to what to fix here - return err; - } -} - export class MaybeLogger { - logger?: winstonNs.Logger + logger?: FoxLogger - constructor(logger?: winstonNs.Logger, label?: string) { + constructor(logger?: FoxLogger, label?: string) { if (logger !== undefined && label !== undefined) { - this.logger = logger.child({labels: [label]}, mergeArr); + this.logger = childLogger(logger, label); } else { this.logger = logger; } diff --git a/src/backend/common/vendor/AbstractApiClient.ts b/src/backend/common/vendor/AbstractApiClient.ts index fba86e1c..8357e7d1 100644 --- a/src/backend/common/vendor/AbstractApiClient.ts +++ b/src/backend/common/vendor/AbstractApiClient.ts @@ -1,7 +1,5 @@ -import { mergeArr } from "../../utils.js"; -import {Logger} from '@foxxmd/winston'; -import { FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; -import winston from '@foxxmd/winston'; +import {childLogger, Logger} from "@foxxmd/logging"; +import {AbstractApiOptions, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; import { PlayObject } from "../../../core/Atomic.js"; import { capitalize } from "../../../core/StringUtils.js"; @@ -18,13 +16,17 @@ export default abstract class AbstractApiClient { workingCredsPath?: string; redirectUri?: string; - constructor(type: any, name: any, config = {}, options = {}) { + constructor(type: any, name: any, config = {}, options: AbstractApiOptions) { this.type = type; this.name = name; const identifier = `API - ${capitalize(this.type)} - ${name}`; - this.logger = winston.loggers.get('app').child({labels: [identifier]}, mergeArr); + const { + logger: parentLogger, + ...restOptions + } = options; + this.logger = childLogger(parentLogger, identifier); this.config = config; - this.options = options; + this.options = restOptions; } static formatPlayObj(obj: any, options: FormatPlayObjectOptions): PlayObject { diff --git a/src/backend/common/vendor/JRiverApiClient.ts b/src/backend/common/vendor/JRiverApiClient.ts index 848a5420..9868398d 100644 --- a/src/backend/common/vendor/JRiverApiClient.ts +++ b/src/backend/common/vendor/JRiverApiClient.ts @@ -3,7 +3,7 @@ import {JRiverData} from "../infrastructure/config/source/jriver.js"; import request, {Request, Response} from 'superagent'; import xml2js from 'xml2js'; import {ErrorWithCause} from "pony-cause"; -import {DEFAULT_RETRY_MULTIPLIER} from "../infrastructure/Atomic.js"; +import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER} from "../infrastructure/Atomic.js"; const parser = new xml2js.Parser({'async': true}); @@ -97,7 +97,7 @@ export class JRiverApiClient extends AbstractApiClient { token?: string; - constructor(name: any, config: JRiverData, options = {}) { + constructor(name: any, config: JRiverData, options: AbstractApiOptions) { super('JRiver', name, config, options); const { url = 'http://localhost:52199/MCWS/v1/' diff --git a/src/backend/common/vendor/KodiApiClient.ts b/src/backend/common/vendor/KodiApiClient.ts index acde4e7e..4f61e6f6 100644 --- a/src/backend/common/vendor/KodiApiClient.ts +++ b/src/backend/common/vendor/KodiApiClient.ts @@ -5,7 +5,7 @@ import { KodiClient } from 'kodi-api' import normalizeUrl from "normalize-url"; import {URL} from "url"; import { RecentlyPlayedOptions } from "../../sources/AbstractSource.js"; -import { FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; +import {AbstractApiOptions, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; import dayjs from "dayjs"; import { PlayObject } from "../../../core/Atomic.js"; @@ -49,7 +49,7 @@ export class KodiApiClient extends AbstractApiClient { declare client: KodiClient; - constructor(name: any, config: KodiData, options = {}) { + constructor(name: any, config: KodiData, options: AbstractApiOptions) { super('Kodi', name, config, options); const { url = 'http://localhost:8080/jsonrpc' diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index 75b13c3a..d31d7c4c 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -8,7 +8,7 @@ import LastFm, { import AbstractApiClient from "./AbstractApiClient.js"; import dayjs from "dayjs"; import {readJson, removeUndefinedKeys, sleep, writeFile} from "../../utils.js"; -import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; +import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; import { LastfmData } from "../infrastructure/config/client/lastfm.js"; import { PlayObject } from "../../../core/Atomic.js"; import {getNodeNetworkException, isNodeNetworkException} from "../errors/NodeErrors.js"; @@ -36,7 +36,7 @@ export default class LastfmApiClient extends AbstractApiClient { user?: string; declare config: LastfmData; - constructor(name: any, config: Partial & {configDir: string, localUrl: string}, options = {}) { + constructor(name: any, config: Partial & {configDir: string, localUrl: string}, options: AbstractApiOptions) { super('lastfm', name, config, options); const {redirectUri, apiKey, secret, session, configDir} = config; this.redirectUri = `${redirectUri ?? `${config.localUrl}/lastfm/callback`}?state=${name}`; diff --git a/src/backend/common/vendor/ListenbrainzApiClient.ts b/src/backend/common/vendor/ListenbrainzApiClient.ts index 65752229..417836d2 100644 --- a/src/backend/common/vendor/ListenbrainzApiClient.ts +++ b/src/backend/common/vendor/ListenbrainzApiClient.ts @@ -1,7 +1,7 @@ import AbstractApiClient from "./AbstractApiClient.js"; import request, {Request} from 'superagent'; import { ListenBrainzClientData } from "../infrastructure/config/client/listenbrainz.js"; -import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; +import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; import dayjs from "dayjs"; import { stringSameness } from '@foxxmd/string-sameness'; import { combinePartsToString } from "../../utils.js"; @@ -118,7 +118,7 @@ export class ListenbrainzApiClient extends AbstractApiClient { declare config: ListenBrainzClientData; url: string; - constructor(name: any, config: ListenBrainzClientData, options = {}) { + constructor(name: any, config: ListenBrainzClientData, options: AbstractApiOptions) { super('ListenBrainz', name, config, options); const { url = 'https://api.listenbrainz.org/' diff --git a/src/backend/common/vendor/chromecast/interfaces.ts b/src/backend/common/vendor/chromecast/interfaces.ts index dbb59595..41494d1c 100644 --- a/src/backend/common/vendor/chromecast/interfaces.ts +++ b/src/backend/common/vendor/chromecast/interfaces.ts @@ -1,7 +1,7 @@ import {createPlatform, MediaController, PersistentClient} from "@foxxmd/chromecast-client"; import { FormatPlayObjectOptions } from "../../infrastructure/Atomic.js"; import {Dayjs} from "dayjs"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; export type PlatformType = ReturnType; export interface PlatformApplication { diff --git a/src/backend/index.ts b/src/backend/index.ts index 58fdb3ff..559687fd 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -1,5 +1,5 @@ import 'dotenv/config'; -import {Logger} from '@foxxmd/winston'; +import {LogDataPretty, Logger} from "@foxxmd/logging"; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc.js'; import isBetween from 'dayjs/plugin/isBetween.js'; @@ -12,15 +12,13 @@ import { projectDir } from "./common/index.js"; import SpotifySource from "./sources/SpotifySource.js"; import { AIOConfig } from "./common/infrastructure/config/aioConfig.js"; import { getRoot } from "./ioc.js"; -import { getLogger } from "./common/logging.js"; -import { LogInfo } from "../core/Atomic.js"; +import {appLogger, initLogger as getInitLogger} from "./common/logging.js"; import { initServer } from "./server/index.js"; import {SimpleIntervalJob, ToadScheduler} from "toad-scheduler"; import { createHeartbeatSourcesTask } from "./tasks/heartbeatSources.js"; import { createHeartbeatClientsTask } from "./tasks/heartbeatClients.js"; import {ErrorWithCause} from "pony-cause"; -import LastfmSource from "./sources/LastfmSource.js"; - +import {loggerDebug, childLogger, LogData, Logger as FoxLogger} from '@foxxmd/logging'; dayjs.extend(utc) dayjs.extend(isBetween); @@ -32,15 +30,16 @@ dayjs.extend(timezone); const scheduler = new ToadScheduler() -let output: LogInfo[] = [] +let output: LogDataPretty[] = [] -const initLogger = getLogger({file: false}, 'init'); -initLogger.stream().on('log', (log: LogInfo) => { - output.unshift(log); - output = output.slice(0, 301); +const [parentInitLogger, initLoggerStream] = getInitLogger(); +const initLogger = childLogger(parentInitLogger, 'Init'); +initLoggerStream.on('data', (log: LogDataPretty) => { +output.unshift(log); +output = output.slice(0, 301); }); -let logger: Logger; +let logger: FoxLogger; process.on('uncaughtExceptionMonitor', (err, origin) => { const appError = new ErrorWithCause(`Uncaught exception is crashing the app! :( Type: ${origin}`, {cause: err}); @@ -79,12 +78,13 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`) process.env.DEBUG_MODE = b.toString(); } - const root = getRoot(config); - initLogger.info(`Version: ${root.get('version')}`); + const [aLogger, appLoggerStream] = await appLogger(logging) + logger = childLogger(aLogger, 'App'); - logger = getLogger(logging, 'app'); + const root = getRoot({...config, logger}); + initLogger.info(`Version: ${root.get('version')}`); - initServer(logger, output); + initServer(logger, appLoggerStream, output); 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/ioc.ts b/src/backend/ioc.ts index 6ed2c918..31312f98 100644 --- a/src/backend/ioc.ts +++ b/src/backend/ioc.ts @@ -9,6 +9,7 @@ import { logPath } from "./common/logging.js"; import { WildcardEmitter } from "./common/WildcardEmitter.js"; import normalizeUrl from 'normalize-url'; import fs from 'fs'; +import {Logger} from "@foxxmd/logging"; let version = 'Unknown'; @@ -46,6 +47,7 @@ let root: ReturnType; export interface RootOptions { baseUrl?: string, port?: string | number + logger: Logger } const createRoot = (options?: RootOptions) => { @@ -71,9 +73,9 @@ const createRoot = (options?: RootOptions) => { localUrl = `${u.origin}:${items.mainPort}`; } return { - clients: () => new ScrobbleClients(items.clientEmitter, items.sourceEmitter, localUrl, items.configDir), - sources: () => new ScrobbleSources(items.sourceEmitter, localUrl, items.configDir), - notifiers: () => new Notifiers(items.notifierEmitter, items.clientEmitter, items.sourceEmitter), + clients: () => new ScrobbleClients(items.clientEmitter, items.sourceEmitter, localUrl, items.configDir, options.logger), + sources: () => new ScrobbleSources(items.sourceEmitter, localUrl, items.configDir, options.logger), + notifiers: () => new Notifiers(items.notifierEmitter, items.clientEmitter, items.sourceEmitter, options.logger), localUrl, hasDefinedBaseUrl: baseUrl !== undefined, isSubPath: u.pathname !== '/' && u.pathname.length > 0 diff --git a/src/backend/notifier/AbstractWebhookNotifier.ts b/src/backend/notifier/AbstractWebhookNotifier.ts index 4325baf1..bba6be35 100644 --- a/src/backend/notifier/AbstractWebhookNotifier.ts +++ b/src/backend/notifier/AbstractWebhookNotifier.ts @@ -1,5 +1,5 @@ import { GotifyConfig, NtfyConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; -import {Logger} from '@foxxmd/winston'; +import {childLogger, Logger} from "@foxxmd/logging"; import { mergeArr } from "../utils.js"; export abstract class AbstractWebhookNotifier { @@ -14,7 +14,7 @@ export abstract class AbstractWebhookNotifier { protected constructor(type: string, defaultName: string, config: GotifyConfig | NtfyConfig, logger: Logger) { this.config = config; const label = `${type} - ${config.name ?? defaultName}` - this.logger = logger.child({labels: [label]}, mergeArr); + this.logger = childLogger(logger, label); } initialize = async () => { diff --git a/src/backend/notifier/GotifyWebhookNotifier.ts b/src/backend/notifier/GotifyWebhookNotifier.ts index cbeca158..f6a4af28 100644 --- a/src/backend/notifier/GotifyWebhookNotifier.ts +++ b/src/backend/notifier/GotifyWebhookNotifier.ts @@ -3,7 +3,7 @@ import { GotifyConfig, PrioritiesConfig, WebhookPayload } from "../common/infras import {gotify} from 'gotify'; import request from 'superagent'; import {HTTPError} from "got"; -import {Logger} from '@foxxmd/winston'; +import {Logger} from "@foxxmd/logging"; export class GotifyWebhookNotifier extends AbstractWebhookNotifier { diff --git a/src/backend/notifier/Notifiers.ts b/src/backend/notifier/Notifiers.ts index 72d39eae..5ddb6cca 100644 --- a/src/backend/notifier/Notifiers.ts +++ b/src/backend/notifier/Notifiers.ts @@ -1,5 +1,4 @@ -import winston, {config, format, Logger} from '@foxxmd/winston'; -import { mergeArr } from "../utils.js"; +import {childLogger, Logger} from '@foxxmd/logging'; import { GotifyConfig, NtfyConfig, WebhookConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; import { GotifyWebhookNotifier } from "./GotifyWebhookNotifier.js"; @@ -17,12 +16,12 @@ export class Notifiers { clientEmitter: EventEmitter; sourceEmitter: EventEmitter; - constructor(emitter: EventEmitter, clientEmitter: EventEmitter, sourceEmitter: EventEmitter) { + constructor(emitter: EventEmitter, clientEmitter: EventEmitter, sourceEmitter: EventEmitter, parentLogger: Logger) { this.emitter = emitter; this.clientEmitter = clientEmitter; this.sourceEmitter = sourceEmitter; - this.logger = winston.loggers.get('app').child({labels: ['Notifiers']}, mergeArr); + this.logger = childLogger(parentLogger, 'Notifiers'); // winston.loggers.get('app').child({labels: ['Notifiers']}, mergeArr); this.sourceEmitter.on('notify', async (payload: WebhookPayload) => { await this.notify(payload); diff --git a/src/backend/notifier/NtfyWebhookNotifier.ts b/src/backend/notifier/NtfyWebhookNotifier.ts index 1ed3a0fb..000cf741 100644 --- a/src/backend/notifier/NtfyWebhookNotifier.ts +++ b/src/backend/notifier/NtfyWebhookNotifier.ts @@ -2,7 +2,7 @@ import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; import { NtfyConfig, PrioritiesConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; import {publish} from 'ntfy'; import request from "superagent"; -import {Logger} from '@foxxmd/winston'; +import {Logger} from "@foxxmd/logging"; import {Config} from "ntfy/interfaces.js"; export class NtfyWebhookNotifier extends AbstractWebhookNotifier { diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index 36a55c9c..c8e8d1ca 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -24,7 +24,7 @@ import { TIME_WEIGHT, TITLE_WEIGHT, } from "../common/infrastructure/Atomic.js"; -import {Logger} from '@foxxmd/winston'; +import {childLogger, Logger} from "@foxxmd/logging"; import { CommonClientConfig } from "../common/infrastructure/config/client/index.js"; import { Notifiers } from "../notifier/Notifiers.js"; import {FixedSizeList} from 'fixed-size-list'; @@ -95,7 +95,7 @@ export default abstract class AbstractScrobbleClient implements Authenticatable this.type = type; this.name = name; this.identifier = `${capitalize(this.type)} - ${name}`; - this.logger = logger.child({labels: [this.identifier]}, mergeArr); + this.logger = childLogger(logger, this.identifier); this.notifier = notifier; this.emitter = emitter; diff --git a/src/backend/scrobblers/LastfmScrobbler.ts b/src/backend/scrobblers/LastfmScrobbler.ts index 87538e4f..16465a68 100644 --- a/src/backend/scrobblers/LastfmScrobbler.ts +++ b/src/backend/scrobblers/LastfmScrobbler.ts @@ -13,7 +13,7 @@ import { FormatPlayObjectOptions, INITIALIZING, ScrobbledPlayObject } from "../c import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; import {TrackScrobblePayload, TrackScrobbleResponse, UserGetRecentTracksResponse} from "lastfm-node-client"; import { Notifiers } from "../notifier/Notifiers.js"; -import {Logger} from '@foxxmd/winston'; +import {Logger} from "@foxxmd/logging"; import { PlayObject, TrackStringOptions } from "../../core/Atomic.js"; import { buildTrackString, capitalize } from "../../core/StringUtils.js"; import EventEmitter from "events"; diff --git a/src/backend/scrobblers/ListenbrainzScrobbler.ts b/src/backend/scrobblers/ListenbrainzScrobbler.ts index d323e996..c6355421 100644 --- a/src/backend/scrobblers/ListenbrainzScrobbler.ts +++ b/src/backend/scrobblers/ListenbrainzScrobbler.ts @@ -3,7 +3,7 @@ import dayjs from 'dayjs'; import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; import { FormatPlayObjectOptions, INITIALIZING } from "../common/infrastructure/Atomic.js"; import { Notifiers } from "../notifier/Notifiers.js"; -import {Logger} from '@foxxmd/winston'; +import {Logger} from "@foxxmd/logging"; import { ListenBrainzClientConfig } from "../common/infrastructure/config/client/listenbrainz.js"; import { ListenbrainzApiClient, ListenPayload } from "../common/vendor/ListenbrainzApiClient.js"; import { PlayObject, TrackStringOptions } from "../../core/Atomic.js"; @@ -23,7 +23,7 @@ export default class ListenbrainzScrobbler extends AbstractScrobbleClient { constructor(name: any, config: ListenBrainzClientConfig, options = {}, notifier: Notifiers, emitter: EventEmitter, logger: Logger) { super('listenbrainz', name, config, notifier, emitter, logger); - this.api = new ListenbrainzApiClient(name, config.data); + this.api = new ListenbrainzApiClient(name, config.data, {logger: this.logger}); } formatPlayObj = (obj: any, options: FormatPlayObjectOptions = {}) => ListenbrainzApiClient.formatPlayObj(obj, options); diff --git a/src/backend/scrobblers/MalojaScrobbler.ts b/src/backend/scrobblers/MalojaScrobbler.ts index c0ead014..9cb27e6e 100644 --- a/src/backend/scrobblers/MalojaScrobbler.ts +++ b/src/backend/scrobblers/MalojaScrobbler.ts @@ -6,7 +6,7 @@ import { sleep, parseRetryAfterSecsFromObj } from "../utils.js"; import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, INITIALIZING } from "../common/infrastructure/Atomic.js"; import { MalojaClientConfig } from "../common/infrastructure/config/client/maloja.js"; import { Notifiers } from "../notifier/Notifiers.js"; -import {Logger} from '@foxxmd/winston'; +import {Logger} from "@foxxmd/logging"; import { getMalojaResponseError, isMalojaAPIErrorBody, diff --git a/src/backend/scrobblers/ScrobbleClients.ts b/src/backend/scrobblers/ScrobbleClients.ts index b597d984..42280b2c 100644 --- a/src/backend/scrobblers/ScrobbleClients.ts +++ b/src/backend/scrobblers/ScrobbleClients.ts @@ -1,11 +1,7 @@ /* eslint-disable no-case-declarations */ import dayjs, {Dayjs} from "dayjs"; import { - createAjvFactory, - mergeArr, - playObjDataMatch, readJson, - returnDuplicateStrings, validateJson, } from "../utils.js"; import MalojaScrobbler from "./MalojaScrobbler.js"; @@ -19,13 +15,10 @@ import { MalojaClientConfig } from "../common/infrastructure/config/client/maloj import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; import { Notifiers } from "../notifier/Notifiers.js"; import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; -import {EventEmitter} from "events"; -import winston, {Logger} from '@foxxmd/winston'; +import {childLogger, Logger} from '@foxxmd/logging'; import ListenbrainzScrobbler from "./ListenbrainzScrobbler.js"; import { ListenBrainzClientConfig } from "../common/infrastructure/config/client/listenbrainz.js"; -import {ErrorWithCause} from "pony-cause"; import { PlayObject } from "../../core/Atomic.js"; -import { buildTrackString } from "../../core/StringUtils.js"; import { WildcardEmitter } from "../common/WildcardEmitter.js"; type groupedNamedConfigs = {[key: string]: ParsedConfig[]}; @@ -44,12 +37,12 @@ export default class ScrobbleClients { sourceEmitter: WildcardEmitter; - constructor(emitter: WildcardEmitter, sourceEmitter: WildcardEmitter, localUrl: string, configDir: string) { + constructor(emitter: WildcardEmitter, sourceEmitter: WildcardEmitter, localUrl: string, configDir: string, parentLogger: Logger) { this.emitter = emitter; this.sourceEmitter = sourceEmitter; this.configDir = configDir; this.localUrl = localUrl; - this.logger = winston.loggers.get('app').child({labels: ['Scrobblers']}, mergeArr); + this.logger = childLogger(parentLogger, 'Scrobblers'); // winston.loggers.get('app').child({labels: ['Scrobblers']}, mergeArr); this.sourceEmitter.on('discoveredToScrobble', async (payload: { data: (PlayObject | PlayObject[]), options: { forceRefresh?: boolean, checkTime?: Dayjs, scrobbleTo?: string[], scrobbleFrom?: string } }) => { await this.scrobble(payload.data, payload.options); diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index 27e64dfe..97fda5c2 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -4,19 +4,13 @@ import { makeClientCheckMiddle, makeSourceCheckMiddle } from "./middleware.js"; import AbstractSource from "../sources/AbstractSource.js"; import { ClientStatusData, - DeadLetterScrobble, - LogInfo, - LogInfoJson, - LogLevel, + DeadLetterScrobble, LeveledLogData, LogOutputConfig, PlayObject, SOURCE_SOT, SourceStatusData, } from "../../core/Atomic.js"; -import {Logger} from "@foxxmd/winston"; -import { formatLogToHtml, getLogger, isLogLevelMinLevel, isLogLineMinLevel } from "../common/logging.js"; -import {MESSAGE} from "triple-beam"; +import {LogDataPretty, Logger, LogLevel} from "@foxxmd/logging"; import {Transform} from "stream"; -//import {createSession} from "better-sse"; import * as bsse from 'better-sse'; import bsseDef from 'better-sse'; import { setupTautulliRoutes } from "./tautulliRoutes.js"; @@ -24,47 +18,42 @@ import { setupPlexRoutes } from "./plexRoutes.js"; import { setupJellyfinRoutes } from "./jellyfinRoutes.js"; import { setupDeezerRoutes } from "./deezerRoutes.js"; import { setupAuthRoutes } from "./auth.js"; -import { ExpressHandler, ScrobbledPlayObject } from "../common/infrastructure/Atomic.js"; +import {ExpressHandler} from "../common/infrastructure/Atomic.js"; import MemorySource from "../sources/MemorySource.js"; import { capitalize } from "../../core/StringUtils.js"; -import {source} from "common-tags"; import AbstractScrobbleClient from "../scrobblers/AbstractScrobbleClient.js"; import { sortByNewestPlayDate } from "../utils.js"; import bodyParser from "body-parser"; import { setupWebscrobblerRoutes } from "./webscrobblerRoutes.js"; import {FixedSizeList} from 'fixed-size-list'; +import {PassThrough} from "node:stream"; const maxBufferSize = 300; -const output: { - [key in LogLevel]: FixedSizeList -} = { - 'debug': new FixedSizeList(maxBufferSize), - 'verbose': new FixedSizeList(maxBufferSize), - 'info': new FixedSizeList(maxBufferSize), - 'warn': new FixedSizeList(maxBufferSize), - 'error': new FixedSizeList(maxBufferSize), -} +const output: Record> = {}; -const addToLogBuffer = (log: LogInfo) => { - output[log.level as LogLevel].add(log); +const createAddToLogBuffer = (levelMap: {[p: number]: string}) => (log: LogDataPretty) => { + output[log.level].add({...log, levelLabel: levelMap[log.level]}); } -const getLogs = (minLevel: LogLevel, limit: number = maxBufferSize, sort: 'asc' | 'desc' = 'desc'): LogInfo[] => { - const allLogs: LogInfo[][] = []; +const getLogs = (minLevel: number, limit: number = maxBufferSize, sort: 'asc' | 'desc' = 'desc'): LeveledLogData[] => { + const allLogs: LeveledLogData[][] = []; for(const level of Object.keys(output)) { - if(isLogLevelMinLevel(level as LogLevel, minLevel)) { + if(Number.parseInt(level) >= minLevel) { allLogs.push(output[level].data); } } if(sort === 'desc') { - return allLogs.flat(1).sort((a, b) => b.id - a.id).slice(0, limit); + return allLogs.flat(1).sort((a, b) => b.time - a.time).slice(0, limit); } - return allLogs.flat(1).sort((a, b) => a.id - b.id).slice(0, limit); + return allLogs.flat(1).sort((a, b) => a.time - b.time).slice(0, limit); } -const availableLevels: LogLevel[] = ['error', 'warn', 'info', 'verbose', 'debug']; +export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: PassThrough, initialLogOutput: LogDataPretty[] = []) => { + for(const level of Object.keys(logger.levels.labels)) { + output[level] = new FixedSizeList(maxBufferSize); + } -export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput: LogInfo[] = []) => { + const addToLogBuffer = createAddToLogBuffer(logger.levels.labels); for(const log of initialLogOutput) { addToLogBuffer(log); } @@ -91,11 +80,10 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput console.log(e); } - const appLogger = getLogger({}, 'app'); - appLogger.stream().on('log', (log: LogInfo) => { + appLoggerStream.on('data', (log: LogDataPretty) => { addToLogBuffer(log); - if(isLogLineMinLevel(log, logConfig.level)) { - logObjectStream.write({message: log[MESSAGE], level: log.level}); + if(log.level >= logger.levels.values[logConfig.level]) { + logObjectStream.write({message: log.line, level: log.level, levelLabel: logger.levels.labels[log.level]}); } }); @@ -128,21 +116,19 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput }); app.get('/api/logs', setLogWebSettings, async (req, res) => { - const slicedLog = getLogs(logConfig.level, logConfig.limit + 1, logConfig.sort === 'ascending' ? 'asc' : 'desc'); - const jsonLogs: LogInfoJson[] = slicedLog.map(x => ({...x, formattedMessage: x[MESSAGE]})); - return res.json({data: jsonLogs, settings: logConfig}); + const slicedLog = getLogs(logger.levels.values[logConfig.level], logConfig.limit + 1, logConfig.sort === 'ascending' ? 'asc' : 'desc'); + return res.json({data: slicedLog, settings: logConfig}); }); app.put('/api/logs', async (req, res) => { logConfig.level = req.body.level as LogLevel | undefined ?? logConfig.level; logConfig.limit = req.body.limit ?? logConfig.limit; - const slicedLog = getLogs(logConfig.level, logConfig.limit + 1, logConfig.sort === 'ascending' ? 'asc' : 'desc'); + const slicedLog = getLogs(logger.levels.values[logConfig.level], logConfig.limit + 1, logConfig.sort === 'ascending' ? 'asc' : 'desc'); // @ts-expect-error logLevel not part of session req.session.logLevel = logConfig.level; // @ts-expect-error limit not part of session req.session.limit = logConfig.limit; - const jsonLogs: LogInfoJson[] = slicedLog.map(x => ({...x, formattedMessage: x[MESSAGE]})); - return res.json({data: jsonLogs, settings: logConfig}); + return res.json({data: slicedLog, settings: logConfig}); }); app.get('/api/events', async (req, res) => { diff --git a/src/backend/server/auth.ts b/src/backend/server/auth.ts index 724cb99b..ecca9f87 100644 --- a/src/backend/server/auth.ts +++ b/src/backend/server/auth.ts @@ -1,5 +1,5 @@ import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import passport from "passport"; import { ExpressHandler } from "../common/infrastructure/Atomic.js"; diff --git a/src/backend/server/deezerRoutes.ts b/src/backend/server/deezerRoutes.ts index d4b4ae81..5c960363 100644 --- a/src/backend/server/deezerRoutes.ts +++ b/src/backend/server/deezerRoutes.ts @@ -1,7 +1,7 @@ import { ExpressHandler } from "../common/infrastructure/Atomic.js"; import { mergeArr, parseBool, sleep } from "../utils.js"; import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import PlexSource, { plexRequestMiddle } from "../sources/PlexSource.js"; import { PlexNotifier } from "../sources/ingressNotifiers/PlexNotifier.js"; diff --git a/src/backend/server/index.ts b/src/backend/server/index.ts index 47a56742..eb6330a5 100644 --- a/src/backend/server/index.ts +++ b/src/backend/server/index.ts @@ -4,23 +4,21 @@ import ViteExpress from "vite-express"; import bodyParser from 'body-parser'; import passport from 'passport'; import session from 'express-session'; -import path from "path"; import { getRoot } from "../ioc.js"; -import {Logger} from "@foxxmd/winston"; -import { LogInfo } from "../../core/Atomic.js"; import { setupApi } from "./api.js"; import { getAddress, mergeArr, parseBool } from "../utils.js"; import {stripIndents} from "common-tags"; import {ErrorWithCause} from "pony-cause"; - -//const buildDir = path.join(process.cwd() + "/build"); +import {childLogger, LogData, LogDataPretty} from "@foxxmd/logging"; +import {PassThrough} from "node:stream"; +import {Logger} from '@foxxmd/logging'; const app = addAsync(express()); const router = Router(); -export const initServer = async (parentLogger: Logger, initialOutput: LogInfo[] = []) => { +export const initServer = async (parentLogger: Logger, appLoggerStream: PassThrough, initialOutput: LogDataPretty[] = []) => { - const logger = parentLogger.child({labels: ['API']}, mergeArr); + const logger = childLogger(parentLogger, 'API'); // parentLogger.child({labels: ['API']}, mergeArr); try { app.use(router); @@ -44,7 +42,7 @@ export const initServer = async (parentLogger: Logger, initialOutput: LogInfo[] const local = root.get('localUrl'); const localDefined = root.get('hasDefinedBaseUrl'); - setupApi(app, logger, initialOutput); + setupApi(app, logger, appLoggerStream, initialOutput); const addy = getAddress(); const addresses: string[] = []; diff --git a/src/backend/server/jellyfinRoutes.ts b/src/backend/server/jellyfinRoutes.ts index 8ae3b81a..b356f950 100644 --- a/src/backend/server/jellyfinRoutes.ts +++ b/src/backend/server/jellyfinRoutes.ts @@ -1,6 +1,6 @@ import { parseBool, remoteHostIdentifiers } from "../utils.js"; import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import bodyParser from "body-parser"; import { JellyfinNotifier } from "../sources/ingressNotifiers/JellyfinNotifier.js"; @@ -17,7 +17,7 @@ export const setupJellyfinRoutes = (app: ExpressWithAsync, logger: Logger, scrob // req.rawBody = buf.toString(); // } }); - const jellyIngress = new JellyfinNotifier(); + const jellyIngress = new JellyfinNotifier(logger); app.postAsync('/jellyfin', async (req, res) => { res.redirect(307, '/api/jellyfin/ingress'); }); diff --git a/src/backend/server/middleware.ts b/src/backend/server/middleware.ts index 876b8ddf..8e0b58df 100644 --- a/src/backend/server/middleware.ts +++ b/src/backend/server/middleware.ts @@ -1,5 +1,5 @@ import { ExpressHandler } from "../common/infrastructure/Atomic.js"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; export const makeSourceCheckMiddle = (sources: any) => (required: boolean ): ExpressHandler => (req: any, res: any, next: any) => { const { diff --git a/src/backend/server/plexRoutes.ts b/src/backend/server/plexRoutes.ts index 0ef26f99..c1341c84 100644 --- a/src/backend/server/plexRoutes.ts +++ b/src/backend/server/plexRoutes.ts @@ -1,16 +1,16 @@ import { ExpressHandler } from "../common/infrastructure/Atomic.js"; import { mergeArr, parseBool } from "../utils.js"; import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {childLogger, Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import PlexSource, { plexRequestMiddle } from "../sources/PlexSource.js"; import { PlexNotifier } from "../sources/ingressNotifiers/PlexNotifier.js"; export const setupPlexRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { - const plexMiddle = plexRequestMiddle(); - const plexLog = logger.child({labels: ['Plex Request']}, mergeArr); - const plexIngress = new PlexNotifier(); + const plexMiddle = plexRequestMiddle(logger); + const plexLog = childLogger(logger, 'Plex Request'); + const plexIngress = new PlexNotifier(logger); const plexIngressMiddle: ExpressHandler = async (req, res, next) => { // track request before parsing body to ensure we at least log that something is happening // (in the event body parsing does not work or request is not POST/PATCH) diff --git a/src/backend/server/tautulliRoutes.ts b/src/backend/server/tautulliRoutes.ts index 01d32e06..cac0ba10 100644 --- a/src/backend/server/tautulliRoutes.ts +++ b/src/backend/server/tautulliRoutes.ts @@ -3,12 +3,12 @@ import { ExpressHandler } from "../common/infrastructure/Atomic.js"; import TautulliSource from "../sources/TautulliSource.js"; import { parseBool } from "../utils.js"; import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; export const setupTautulliRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { - const tauIngress = new TautulliNotifier(); + const tauIngress = new TautulliNotifier(logger); const tautulliIngressRoute: ExpressHandler = async function(this: any, req, res) { tauIngress.trackIngress(req, false); diff --git a/src/backend/server/webscrobblerRoutes.ts b/src/backend/server/webscrobblerRoutes.ts index 174a81c2..18b4956c 100644 --- a/src/backend/server/webscrobblerRoutes.ts +++ b/src/backend/server/webscrobblerRoutes.ts @@ -1,6 +1,6 @@ import { mergeArr, parseBool, remoteHostIdentifiers } from "../utils.js"; import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {childLogger, Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import bodyParser from "body-parser"; import { WebScrobblerPayload } from "../common/vendor/webscrobbler/interfaces.js"; @@ -11,7 +11,7 @@ import path from "path"; export const setupWebscrobblerRoutes = (app: ExpressWithAsync, parentLogger: Logger, scrobbleSources: ScrobbleSources) => { - const logger = parentLogger.child({labels: ['Ingress', 'WebScrobbler']}, mergeArr); + const logger = childLogger(parentLogger, ['Ingress', 'WebScrobbler']); const webScrobblerJsonParser = bodyParser.json({ type: ['text/*', 'application/json'], @@ -21,7 +21,7 @@ export const setupWebscrobblerRoutes = (app: ExpressWithAsync, parentLogger: Log // req.rawBody = buf.toString(); // } }); - const webhookIngress = new WebhookNotifier(); + const webhookIngress = new WebhookNotifier(logger); app.postAsync('/api/webscrobbler*', async (req, res, next) => { // track request before parsing body to ensure we at least log that something is happening diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index 032f40ee..0c9b837c 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -28,7 +28,7 @@ import { SINGLE_USER_PLATFORM_ID, SourceType, } from "../common/infrastructure/Atomic.js"; -import {Logger} from '@foxxmd/winston'; +import {childLogger, Logger} from '@foxxmd/logging'; import { SourceConfig } from "../common/infrastructure/config/source/sources.js"; import {EventEmitter} from "events"; import {FixedSizeList} from "fixed-size-list"; @@ -91,7 +91,7 @@ export default abstract class AbstractSource implements Authenticatable { this.type = type; this.name = name; this.identifier = `Source - ${capitalize(this.type)} - ${name}`; - this.logger = internal.logger.child({labels: [`${capitalize(this.type)} - ${name}`]}, mergeArr); + this.logger = childLogger(internal.logger, `${capitalize(this.type)} - ${name}`); this.config = config; this.clients = clients; this.instantiatedAt = dayjs(); diff --git a/src/backend/sources/ChromecastSource.ts b/src/backend/sources/ChromecastSource.ts index 620c25e6..c5cef8c6 100644 --- a/src/backend/sources/ChromecastSource.ts +++ b/src/backend/sources/ChromecastSource.ts @@ -24,13 +24,14 @@ import { getMediaStatus, genPlayHash, } from "../common/vendor/chromecast/ChromecastClientUtils.js"; -import {config, Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import {ContextualValidationError} from "@foxxmd/chromecast-client/dist/cjs/src/utils.js"; import { buildTrackString } from "../../core/StringUtils.js"; import { discoveryAvahi, discoveryNative } from "../utils/MDNSUtils.js"; import {MaybeLogger} from "../common/logging.js"; import e, {application} from "express"; import {NETWORK_ERROR_FAILURE_CODES} from "../common/errors/NodeErrors.js"; +import {childLogger} from "@foxxmd/logging"; interface ChromecastDeviceInfo { mdns: MdnsDeviceInfo @@ -236,7 +237,7 @@ export class ChromecastSource extends MemorySource { retries: 0, platform, applications, - logger: this.logger.child({labels: [device.name.substring(0, 25)]}, mergeArr), + logger: childLogger(this.logger, device.name.substring(0, 25)), }); } catch (e) { this.logger.error(e); @@ -392,7 +393,7 @@ export class ChromecastSource extends MemorySource { badData: false, validAppType: valid, playerId: genGroupIdStr([genDeviceId(k, a.displayName), NO_USER]), - logger: v.logger.child({labels: [`App ${a.displayName.substring(0, 25)}-${a.transportId.substring(0,4)}`]}, mergeArr) + logger: childLogger(v.logger, `App ${a.displayName.substring(0, 25)}-${a.transportId.substring(0,4)}`) } v.applications.set(a.transportId, storedApp); } else if(storedApp.stale === true) { diff --git a/src/backend/sources/JRiverSource.ts b/src/backend/sources/JRiverSource.ts index 39ea66cf..a0d720dd 100644 --- a/src/backend/sources/JRiverSource.ts +++ b/src/backend/sources/JRiverSource.ts @@ -32,7 +32,7 @@ export class JRiverSource extends MemorySource { } = {}, } = config; this.url = JRiverSource.parseConnectionUrl(url); - this.client = new JRiverApiClient(name, {...data, url: this.url.toString()}); + this.client = new JRiverApiClient(name, {...data, url: this.url.toString()}, {logger: this.logger}); this.requiresAuth = true; this.canPoll = true; this.multiPlatform = true; diff --git a/src/backend/sources/JellyfinSource.ts b/src/backend/sources/JellyfinSource.ts index 6b4c4562..840dead8 100644 --- a/src/backend/sources/JellyfinSource.ts +++ b/src/backend/sources/JellyfinSource.ts @@ -11,7 +11,7 @@ import { JellySourceConfig } from "../common/infrastructure/config/source/jellyf import { FormatPlayObjectOptions, InternalConfig, PlayPlatformId } from "../common/infrastructure/Atomic.js"; import EventEmitter from "events"; import { PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import { JellyfinPlayerState } from "./PlayerState/JellyfinPlayerState.js"; import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; import { buildTrackString, splitByFirstFound, truncateStringToLength } from "../../core/StringUtils.js"; diff --git a/src/backend/sources/KodiSource.ts b/src/backend/sources/KodiSource.ts index b63441ab..b6329f95 100644 --- a/src/backend/sources/KodiSource.ts +++ b/src/backend/sources/KodiSource.ts @@ -32,7 +32,7 @@ export class KodiSource extends MemorySource { url } = {} } = this.config; - this.client = new KodiApiClient(this.name, this.config.data); + this.client = new KodiApiClient(this.name, this.config.data, {logger: this.logger}); this.logger.debug(`Config URL: '${url ?? '(None Given)'}' => Normalized: '${this.client.url.toString()}'`) return true; } diff --git a/src/backend/sources/LastfmSource.ts b/src/backend/sources/LastfmSource.ts index d5e8daaa..81a0865a 100644 --- a/src/backend/sources/LastfmSource.ts +++ b/src/backend/sources/LastfmSource.ts @@ -33,7 +33,7 @@ export default class LastfmSource extends MemorySource { this.canBacklog = true; this.supportsUpstreamRecentlyPlayed = true; this.supportsUpstreamNowPlaying = true; - this.api = new LastfmApiClient(name, {...config.data, configDir: internal.configDir, localUrl: internal.localUrl}); + this.api = new LastfmApiClient(name, {...config.data, configDir: internal.configDir, localUrl: internal.localUrl}, {logger: this.logger}); this.playerSourceOfTruth = SOURCE_SOT.HISTORY; this.logger.info(`Note: The player for this source is an analogue for the 'Now Playing' status exposed by ${this.type} which is NOT used for scrobbling. Instead, the 'recently played' or 'history' information provided by this source is used for scrobbles.`) } diff --git a/src/backend/sources/ListenbrainzSource.ts b/src/backend/sources/ListenbrainzSource.ts index 94dc6528..9566c156 100644 --- a/src/backend/sources/ListenbrainzSource.ts +++ b/src/backend/sources/ListenbrainzSource.ts @@ -28,7 +28,7 @@ export default class ListenbrainzSource extends MemorySource { super('listenbrainz', name, {...config, data: {interval, maxInterval, ...restData}}, internal, emitter); this.canPoll = true; this.canBacklog = true; - this.api = new ListenbrainzApiClient(name, config.data); + this.api = new ListenbrainzApiClient(name, config.data, {logger: this.logger}); this.playerSourceOfTruth = SOURCE_SOT.HISTORY; this.supportsUpstreamRecentlyPlayed = true; this.logger.info(`Note: The player for this source is an analogue for the 'Now Playing' status exposed by ${this.type} which is NOT used for scrobbling. Instead, the 'recently played' or 'history' information provided by this source is used for scrobbles.`) diff --git a/src/backend/sources/MemorySource.ts b/src/backend/sources/MemorySource.ts index 1dc2a252..9c75dfbd 100644 --- a/src/backend/sources/MemorySource.ts +++ b/src/backend/sources/MemorySource.ts @@ -27,7 +27,7 @@ import { import TupleMap from "../common/TupleMap.js"; import {AbstractPlayerState, createPlayerOptions, PlayerStateOptions} from "./PlayerState/AbstractPlayerState.js"; import { GenericPlayerState } from "./PlayerState/GenericPlayerState.js"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import {PlayObject, SOURCE_SOT, SOURCE_SOT_TYPES, SourcePlayerObj} from "../../core/Atomic.js"; import { buildTrackString } from "../../core/StringUtils.js"; import {SimpleIntervalJob, Task, ToadScheduler} from "toad-scheduler"; diff --git a/src/backend/sources/MopidySource.ts b/src/backend/sources/MopidySource.ts index 486f01e6..932e4d48 100644 --- a/src/backend/sources/MopidySource.ts +++ b/src/backend/sources/MopidySource.ts @@ -12,11 +12,11 @@ import {URL} from "url"; import normalizeUrl from 'normalize-url'; import {EventEmitter} from "events"; import pEvent from 'p-event'; -import winston from '@foxxmd/winston'; import { RecentlyPlayedOptions } from "./AbstractSource.js"; import { PlayObject } from "../../core/Atomic.js"; import { buildTrackString } from "../../core/StringUtils.js"; import {ErrorWithCause} from "pony-cause"; +import {loggerTest} from "@foxxmd/logging"; export class MopidySource extends MemorySource { declare config: MopidySourceConfig; @@ -58,7 +58,7 @@ export class MopidySource extends MemorySource { autoConnect: false, webSocketUrl: this.url.toString(), // @ts-expect-error logger satisfies but is missing types not used - console: winston.loggers.get('noop') + console: loggerTest }); this.client.on('state:offline', () => { this.logger.verbose('Lost connection to server'); diff --git a/src/backend/sources/PlayerState/AbstractPlayerState.ts b/src/backend/sources/PlayerState/AbstractPlayerState.ts index 3fb936ed..5db90442 100644 --- a/src/backend/sources/PlayerState/AbstractPlayerState.ts +++ b/src/backend/sources/PlayerState/AbstractPlayerState.ts @@ -7,7 +7,7 @@ import { } from "../../common/infrastructure/Atomic.js"; import dayjs, {Dayjs} from "dayjs"; import { formatNumber, genGroupIdStr, playObjDataMatch, progressBar } from "../../utils.js"; -import {Logger} from "@foxxmd/winston"; +import {childLogger, Logger} from "@foxxmd/logging"; import { ListenProgress } from "./ListenProgress.js"; import {PlayObject, Second, SOURCE_SOT, SOURCE_SOT_TYPES, SourcePlayerObj} from "../../../core/Atomic.js"; import { buildTrackString } from "../../../core/StringUtils.js"; @@ -60,7 +60,7 @@ export abstract class AbstractPlayerState { protected constructor(logger: Logger, platformId: PlayPlatformId, opts: PlayerStateOptions = DefaultPlayerStateOptions) { this.platformId = platformId; - this.logger = logger.child({labels: [`Player ${this.platformIdStr}`]}); + this.logger = childLogger(logger, `Player ${this.platformIdStr}`); const { staleInterval = 120, diff --git a/src/backend/sources/PlayerState/GenericPlayerState.ts b/src/backend/sources/PlayerState/GenericPlayerState.ts index caaa5229..3977de6a 100644 --- a/src/backend/sources/PlayerState/GenericPlayerState.ts +++ b/src/backend/sources/PlayerState/GenericPlayerState.ts @@ -1,5 +1,5 @@ import { AbstractPlayerState, PlayerStateOptions } from "./AbstractPlayerState.js"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import { PlayPlatformId } from "../../common/infrastructure/Atomic.js"; export class GenericPlayerState extends AbstractPlayerState { diff --git a/src/backend/sources/PlayerState/JellyfinPlayerState.ts b/src/backend/sources/PlayerState/JellyfinPlayerState.ts index 93d28bfb..b7019cde 100644 --- a/src/backend/sources/PlayerState/JellyfinPlayerState.ts +++ b/src/backend/sources/PlayerState/JellyfinPlayerState.ts @@ -1,5 +1,5 @@ import { GenericPlayerState } from "./GenericPlayerState.js"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import { PlayPlatformId, ReportedPlayerStatus } from "../../common/infrastructure/Atomic.js"; import { PlayerStateOptions } from "./AbstractPlayerState.js"; import { PlayObject } from "../../../core/Atomic.js"; diff --git a/src/backend/sources/PlexSource.ts b/src/backend/sources/PlexSource.ts index 458788db..a5697305 100644 --- a/src/backend/sources/PlexSource.ts +++ b/src/backend/sources/PlexSource.ts @@ -1,14 +1,14 @@ import dayjs from "dayjs"; -import { combinePartsToString, mergeArr } from "../utils.js"; +import { combinePartsToString } from "../utils.js"; import AbstractSource from "./AbstractSource.js"; import formidable from 'formidable'; import concatStream from 'concat-stream'; import { PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; import { FormatPlayObjectOptions, InternalConfig, SourceType } from "../common/infrastructure/Atomic.js"; import EventEmitter from "events"; -import winston from '@foxxmd/winston'; import { PlayObject } from "../../core/Atomic.js"; import { truncateStringToLength } from "../../core/StringUtils.js"; +import {childLogger, Logger} from "@foxxmd/logging"; const shortDeviceId = truncateStringToLength(10, ''); @@ -231,9 +231,9 @@ export default class PlexSource extends AbstractSource { } } -export const plexRequestMiddle = () => { +export const plexRequestMiddle = (logger: Logger) => { - const plexLog = winston.loggers.get('app').child({labels: ['Plex Request']}, mergeArr); + const plexLog = childLogger(logger, 'Plex Request'); return async (req: any, res: any, next: any) => { diff --git a/src/backend/sources/ScrobbleSources.ts b/src/backend/sources/ScrobbleSources.ts index 84075278..13ed28ae 100644 --- a/src/backend/sources/ScrobbleSources.ts +++ b/src/backend/sources/ScrobbleSources.ts @@ -9,7 +9,7 @@ import LastfmSource from "./LastfmSource.js"; import DeezerSource from "./DeezerSource.js"; import { ConfigMeta, InternalConfig, SourceType, sourceTypes } from "../common/infrastructure/Atomic.js"; import { configDir as defaultConfigDir } from "../common/index.js"; -import winston, {Logger} from '@foxxmd/winston'; +import {childLogger, Logger} from '@foxxmd/logging'; import { SourceAIOConfig, SourceConfig } from "../common/infrastructure/config/source/sources.js"; import { DeezerData, DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; @@ -55,11 +55,11 @@ export default class ScrobbleSources { emitter: WildcardEmitter; - constructor(emitter: EventEmitter, localUrl: string, configDir: string = defaultConfigDir) { + constructor(emitter: EventEmitter, localUrl: string, configDir: string = defaultConfigDir, parentLogger: Logger) { this.emitter = emitter; this.configDir = configDir; this.localUrl = localUrl; - this.logger = winston.loggers.get('app').child({labels: ['Sources']}, mergeArr); + this.logger = childLogger(parentLogger, 'Sources'); // winston.loggers.get('app').child({labels: ['Sources']}, mergeArr); } getByName = (name: any) => this.sources.find(x => x.name === name) diff --git a/src/backend/sources/ingressNotifiers/IngressNotifier.ts b/src/backend/sources/ingressNotifiers/IngressNotifier.ts index 3c7ce51a..e5b6df72 100644 --- a/src/backend/sources/ingressNotifiers/IngressNotifier.ts +++ b/src/backend/sources/ingressNotifiers/IngressNotifier.ts @@ -1,5 +1,5 @@ -import winston, {Logger} from '@foxxmd/winston'; -import { mergeArr, remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; +import {childLogger, Logger} from '@foxxmd/logging'; +import { remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; import {Request} from "express"; import { RemoteIdentityParts } from "../../common/infrastructure/Atomic.js"; @@ -11,9 +11,9 @@ export abstract class IngressNotifier { remotes: Record = {}; - protected constructor(name: string) { + protected constructor(name: string, logger: Logger) { this.identifier = name; - this.logger = winston.loggers.get('app').child({labels: ['Ingress', name]}, mergeArr); + this.logger = childLogger(logger, ['Ingress', name]); } public trackIngress(req: Request, isRaw: boolean) { diff --git a/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts b/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts index 8ded82f4..bc91ad69 100644 --- a/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts +++ b/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts @@ -2,11 +2,12 @@ import { IngressNotifier } from "./IngressNotifier.js"; import {Request} from "express"; import JellyfinSource from "../JellyfinSource.js"; import { remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; +import {Logger} from "@foxxmd/logging"; export class JellyfinNotifier extends IngressNotifier { - constructor() { - super('Jellyfin'); + constructor(logger: Logger) { + super('Jellyfin', logger); } seenServers: Record = {}; diff --git a/src/backend/sources/ingressNotifiers/PlexNotifier.ts b/src/backend/sources/ingressNotifiers/PlexNotifier.ts index b9910b3a..079e9c10 100644 --- a/src/backend/sources/ingressNotifiers/PlexNotifier.ts +++ b/src/backend/sources/ingressNotifiers/PlexNotifier.ts @@ -1,11 +1,12 @@ import { IngressNotifier } from "./IngressNotifier.js"; import {Request} from "express"; import PlexSource from "../PlexSource.js"; +import {Logger} from "@foxxmd/logging"; export class PlexNotifier extends IngressNotifier { - constructor() { - super('Plex'); + constructor(logger: Logger) { + super('Plex', logger); } seenServers: string[] = []; diff --git a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts index ef12b2de..5db110a2 100644 --- a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts +++ b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts @@ -2,11 +2,12 @@ import { IngressNotifier } from "./IngressNotifier.js"; import {Request} from "express"; import PlexSource from "../PlexSource.js"; import TautulliSource from "../TautulliSource.js"; +import {Logger} from "@foxxmd/logging"; export class TautulliNotifier extends IngressNotifier { - constructor() { - super('Tautulli'); + constructor(logger: Logger) { + super('Tautulli', logger); } seenServers: string[] = []; diff --git a/src/backend/sources/ingressNotifiers/WebhookNotifier.ts b/src/backend/sources/ingressNotifiers/WebhookNotifier.ts index d11dcde8..d5b22625 100644 --- a/src/backend/sources/ingressNotifiers/WebhookNotifier.ts +++ b/src/backend/sources/ingressNotifiers/WebhookNotifier.ts @@ -1,11 +1,12 @@ import { IngressNotifier } from "./IngressNotifier.js"; import {Request} from "express"; import path from "path"; +import {Logger} from "@foxxmd/logging"; export class WebhookNotifier extends IngressNotifier { - constructor() { - super('WebScrobbler'); + constructor(logger: Logger) { + super('WebScrobbler', logger); } seenSlugs: Record = {}; diff --git a/src/backend/tasks/heartbeatClients.ts b/src/backend/tasks/heartbeatClients.ts index fbe9a7ac..472fcd21 100644 --- a/src/backend/tasks/heartbeatClients.ts +++ b/src/backend/tasks/heartbeatClients.ts @@ -1,11 +1,11 @@ -import {config, Logger} from "@foxxmd/winston"; +import {childLogger, Logger} from '@foxxmd/logging'; import { mergeArr } from "../utils.js"; import {AsyncTask} from "toad-scheduler"; import {PromisePool} from "@supercharge/promise-pool"; import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; export const createHeartbeatClientsTask = (clients: ScrobbleClients, parentLogger: Logger) => { - const logger = parentLogger.child({labels: ['Heartbeat', 'Clients']}, mergeArr); + const logger = childLogger(parentLogger, ['Heartbeat', 'Clients']); return new AsyncTask( 'Heartbeat', diff --git a/src/backend/tasks/heartbeatSources.ts b/src/backend/tasks/heartbeatSources.ts index 8c624986..6d6efb9d 100644 --- a/src/backend/tasks/heartbeatSources.ts +++ b/src/backend/tasks/heartbeatSources.ts @@ -1,4 +1,4 @@ -import {config, Logger} from "@foxxmd/winston"; +import {childLogger, Logger} from '@foxxmd/logging'; import { mergeArr } from "../utils.js"; import {AsyncTask} from "toad-scheduler"; import {PromisePool} from "@supercharge/promise-pool"; @@ -6,7 +6,7 @@ import ScrobbleSources from "../sources/ScrobbleSources.js"; import { ChromecastSource } from "../sources/ChromecastSource.js"; export const createHeartbeatSourcesTask = (sources: ScrobbleSources, parentLogger: Logger) => { - const logger = parentLogger.child({labels: ['Heartbeat', 'Sources']}, mergeArr); + const logger = childLogger(parentLogger, ['Heartbeat', 'Sources']); return new AsyncTask( 'Heartbeat', diff --git a/src/backend/tests/jellyfin/jellyfin.test.ts b/src/backend/tests/jellyfin/jellyfin.test.ts index c704a28d..07770787 100644 --- a/src/backend/tests/jellyfin/jellyfin.test.ts +++ b/src/backend/tests/jellyfin/jellyfin.test.ts @@ -4,8 +4,8 @@ import samplePayload from './playbackProgressSample.json'; import JellyfinSource from "../../sources/JellyfinSource.js"; import EventEmitter from "events"; -import { getLogger } from "../../common/logging.js"; import { JsonPlayObject, PlayObject } from "../../../core/Atomic.js"; +import {loggerTest} from "@foxxmd/logging"; const dataAsFixture = (data: any): TestFixture => { return data as TestFixture; @@ -28,7 +28,7 @@ describe('Jellyfin Payload Parsing', function () { describe('Correctly detects events as valid/invalid', function () { - const jfSource = new JellyfinSource('Test', {data: {}}, {localUrl: 'test', configDir: 'test', logger: getLogger({}, 'noop')}, new EventEmitter()); + const jfSource = new JellyfinSource('Test', {data: {}}, {localUrl: 'test', configDir: 'test', logger: loggerTest}, new EventEmitter()); it('Should parse PlayProgress with Audio ItemType as valid event', async function () { const fixture = dataAsFixture(samplePayload[0]); diff --git a/src/backend/tests/listenbrainz/listenbrainz.test.ts b/src/backend/tests/listenbrainz/listenbrainz.test.ts index 1e0d4ded..8e35fe28 100644 --- a/src/backend/tests/listenbrainz/listenbrainz.test.ts +++ b/src/backend/tests/listenbrainz/listenbrainz.test.ts @@ -20,6 +20,7 @@ import { withRequestInterception } from "../utils/networking.js"; import {http, HttpResponse} from "msw"; import { UpstreamError } from "../../common/errors/UpstreamError.js"; import { ExpectedResults } from "../utils/interfaces.js"; +import {loggerTest} from "@foxxmd/logging"; interface LZTestFixture { data: ListenResponse @@ -112,7 +113,7 @@ describe('Listenbrainz Response Behavior', function() { { token: 'test', username: 'test' - }); + }, {logger: loggerTest}); it('Should recognize bad requests as non-showstopping',withRequestInterception( [ diff --git a/src/backend/tests/player/player.test.ts b/src/backend/tests/player/player.test.ts index c00cdb70..178e3203 100644 --- a/src/backend/tests/player/player.test.ts +++ b/src/backend/tests/player/player.test.ts @@ -2,13 +2,13 @@ import {describe, it} from 'mocha'; import {assert} from 'chai'; import { generatePlay } from "../utils/PlayTestUtils.js"; import { GenericPlayerState } from "../../sources/PlayerState/GenericPlayerState.js"; -import { getLogger } from "../../common/logging.js"; import { CALCULATED_PLAYER_STATUSES, NO_DEVICE, NO_USER, REPORTED_PLAYER_STATUSES } from "../../common/infrastructure/Atomic.js"; import { playObjDataMatch } from "../../utils.js"; import dayjs from "dayjs"; import clone from "clone"; +import {loggerTest} from "@foxxmd/logging"; -const logger = getLogger({}, 'noop'); +const logger = loggerTest; const newPlay = generatePlay({duration: 300}); diff --git a/src/backend/tests/scrobbler/TestScrobbler.ts b/src/backend/tests/scrobbler/TestScrobbler.ts index 355dae61..15787e98 100644 --- a/src/backend/tests/scrobbler/TestScrobbler.ts +++ b/src/backend/tests/scrobbler/TestScrobbler.ts @@ -1,15 +1,15 @@ import AbstractScrobbleClient from "../../scrobblers/AbstractScrobbleClient.js"; import { PlayObject } from "../../../core/Atomic.js"; -import { getLogger } from "../../common/logging.js"; import { Notifiers } from "../../notifier/Notifiers.js"; import EventEmitter from "events"; import request from "superagent"; +import {loggerTest} from "@foxxmd/logging"; export class TestScrobbler extends AbstractScrobbleClient { constructor() { - const logger = getLogger({}, 'noop'); - const notifier = new Notifiers(new EventEmitter(), new EventEmitter(), new EventEmitter()); + const logger = loggerTest; + const notifier = new Notifiers(new EventEmitter(), new EventEmitter(), new EventEmitter(), logger); super('test', 'Test', {name: 'test'}, notifier, new EventEmitter(), logger); } diff --git a/src/backend/utils.ts b/src/backend/utils.ts index a713408e..724298c4 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -1,7 +1,7 @@ import {accessSync, constants, promises} from "fs"; import dayjs, {Dayjs} from 'dayjs'; import utc from 'dayjs/plugin/utc.js'; -import {Logger} from '@foxxmd/winston'; +import {Logger} from '@foxxmd/logging'; import JSON5 from 'json5'; import {TimeoutError, WebapiError} from "spotify-web-api-node/src/response-error.js"; import {Schema} from 'ajv'; diff --git a/src/backend/utils/MDNSUtils.ts b/src/backend/utils/MDNSUtils.ts index 4f05e73a..6d25c332 100644 --- a/src/backend/utils/MDNSUtils.ts +++ b/src/backend/utils/MDNSUtils.ts @@ -1,4 +1,4 @@ -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import AvahiBrowser from 'avahi-browse'; import { MaybeLogger } from "../common/logging.js"; import { sleep } from "../utils.js"; diff --git a/src/client/logs/LogLine.tsx b/src/client/logs/LogLine.tsx index e2dc232f..8c2360bc 100644 --- a/src/client/logs/LogLine.tsx +++ b/src/client/logs/LogLine.tsx @@ -1,17 +1,6 @@ import React, {PropsWithChildren} from 'react'; import {parseLogLine} from "../utils/index"; -const breakSymbol = '
'; - -const replaceLevel = (chunk: string) => { - return chunk.toString().replace('\n', breakSymbol) - .replace(/(debug)\s/gi, '$1 ') - .replace(/(warn)\s/gi, '$1 ') - .replace(/(info)\s/gi, '$1 ') - .replace(/(verbose)\s/gi, '$1 ') - .replace(/(error)\s/gi, '$1 ') - .trim(); -} const getClass = (level: string) => { switch(level) { case 'debug': @@ -26,12 +15,11 @@ const getClass = (level: string) => { return 'error red'; } } -const LogLine = (props: PropsWithChildren<{level: string, message: string}>) => { +const LogLine = (props: PropsWithChildren<{level: number, levelLabel: string, message: string}>) => { const lineParts = parseLogLine(props.message); - const level = props.level ?? lineParts.level; - const levelClass = getClass(level); + const levelClass = getClass(props.levelLabel); return ( -
{lineParts.timestamp} {level.padEnd(7, ' ')} : {lineParts.message}
+
{lineParts.timestamp} {props.levelLabel.padEnd(7, ' ')} : {lineParts.message}
) }; diff --git a/src/client/logs/LogsSection.tsx b/src/client/logs/LogsSection.tsx index 10972f4c..5c02f7dc 100644 --- a/src/client/logs/LogsSection.tsx +++ b/src/client/logs/LogsSection.tsx @@ -3,17 +3,18 @@ import './LogsSection.css'; import {FixedSizeList} from "fixed-size-list"; import {useEventSource, useEventSourceListener} from "@react-nano/use-event-source"; import LogLine from "./LogLine"; -import {useGetLogsQuery, useLazySetLogSettingsQuery, logsApi} from "./logsApi"; +import {useGetLogsQuery, useLazySetLogSettingsQuery} from "./logsApi"; import {connect, ConnectedProps} from "react-redux"; import {RootState} from "../store"; import Loading from "../components/loading/Loading"; -let logBuffer: { message: string, id: string, level: string }[] = []; +const logBuffer: { message: string, id: string, level: number, levelLabel:string }[] = []; interface MinLogInfo { message: string, id: string, - level: string + level: number + levelLabel: string } const createFixedList = (size, initialList: MinLogInfo[] = []): FixedSizeList => { @@ -55,7 +56,7 @@ const LogsSection = (props: PropsFromRedux) => { useGetLogsQuery(undefined); useEffect(() => { - list = createFixedList(settings.limit, logs.map((x, index) => ({...x, message: x.formattedMessage, id: index.toString()}))); + list = createFixedList(settings.limit, logs.map((x, index) => ({...x, message: x.line, id: index.toString()}))); setLogList(Array.from(list.data)); setLogLevel(settings.level); }, [logs, settings, setLogList, setLogLevel]); @@ -73,7 +74,7 @@ const LogsSection = (props: PropsFromRedux) => { useEventSourceListener(eventSource, ['messsage', 'stream'], evt => { const data = JSON.parse(evt.data); // @ts-ignore - list.add({message: data.message, id: evt.lastEventId, level: data.level}); + list.add({message: data.message, id: evt.lastEventId, level: data.level, levelLabel: data.levelLabel}); setLogList(Array.from(list.data)); //console.log(evt); }, [setLogList]); @@ -101,7 +102,7 @@ const LogsSection = (props: PropsFromRedux) => {
{ - logList.map(x => ) + logList.map(x => ) }
diff --git a/src/client/logs/logDucks.ts b/src/client/logs/logDucks.ts index fa53f7c0..1d40f9a4 100644 --- a/src/client/logs/logDucks.ts +++ b/src/client/logs/logDucks.ts @@ -3,9 +3,10 @@ import { createSlice } from '@reduxjs/toolkit' import {logsApi} from "./logsApi"; -import {LogInfoJson, LogOutputConfig} from "../../core/Atomic"; +import {LogOutputConfig} from "../../core/Atomic"; +import {LogDataPretty} from "@foxxmd/logging"; export interface LogsState { - data: LogInfoJson[], + data: (LogDataPretty & {levelLabel: string})[], settings: LogOutputConfig } const initialState: LogsState = {data: [], settings: {level: 'debug', sort: 'asc', limit: 50}}; diff --git a/src/client/logs/logsApi.ts b/src/client/logs/logsApi.ts index 7efeaa94..b191049c 100644 --- a/src/client/logs/logsApi.ts +++ b/src/client/logs/logsApi.ts @@ -1,14 +1,14 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; -import {LogInfoJson, LogOutputConfig} from "../../core/Atomic"; +import {LeveledLogData, LogOutputConfig} from "../../core/Atomic"; export const logsApi = createApi({ reducerPath: 'logsApi', baseQuery: fetchBaseQuery({ baseUrl: './api/' }), endpoints: (builder) => ({ - getLogs: builder.query<{ data: LogInfoJson[], settings: LogOutputConfig }, undefined>({ + getLogs: builder.query<{ data: LeveledLogData[], settings: LogOutputConfig }, undefined>({ query: () => `logs`, }), - setLogSettings: builder.query<{ data: LogInfoJson[], settings: LogOutputConfig }, object>({ + setLogSettings: builder.query<{ data: LeveledLogData[], settings: LogOutputConfig }, object>({ query: (settings) => ({ url: '/logs', method: 'PUT', diff --git a/src/client/utils/index.tsx b/src/client/utils/index.tsx index 8f872ca3..06473dee 100644 --- a/src/client/utils/index.tsx +++ b/src/client/utils/index.tsx @@ -22,7 +22,7 @@ export const buildTrackStringReactOptions: TrackStringOptions = { } } -const LOG_LINE_REGEX = new RegExp(/(?\S+)\s+(?\w+)\s*:\s*(?(?:.|\n)*)/, 'm'); +const LOG_LINE_REGEX = new RegExp(/\[(?.+)]\s+(?\w+)\s*:\s*(?(?:.|\n)*)/m, 'm'); export const parseLogLine = (line: string) => { const match = line.match(LOG_LINE_REGEX); if (match === null) { diff --git a/src/core/Atomic.ts b/src/core/Atomic.ts index a9e724d6..45d3c649 100644 --- a/src/core/Atomic.ts +++ b/src/core/Atomic.ts @@ -1,6 +1,6 @@ import {Dayjs} from "dayjs"; -import {MESSAGE} from "triple-beam"; import {ListenProgress} from "../backend/sources/PlayerState/ListenProgress.js"; +import {LogDataPretty, LogLevel} from "@foxxmd/logging"; export interface SourceStatusData { status: string; @@ -158,29 +158,12 @@ export interface JsonPlayData extends PlayData { playDateCompleted?: Dayjs } -export type LogLevel = "error" | "warn" | "info" | "verbose" | "debug"; -export const logLevels = ['error', 'warn', 'info', 'verbose', 'debug']; - -export interface LogInfo { - id: number - message: string - [MESSAGE]: string, - level: string - timestamp: string - labels?: string[] - transport?: string[] -} - export interface LogOutputConfig { level: LogLevel, sort: string, limit: number } -export interface LogInfoJson extends LogInfo { - formattedMessage: string -} - export interface SourcePlayerObj { platformId: string, play: PlayObject, @@ -243,3 +226,7 @@ export const SOURCE_SOT = { PLAYER : 'player' as SOURCE_SOT_TYPES, HISTORY: 'history' as SOURCE_SOT_TYPES } + +export interface LeveledLogData extends LogDataPretty { + levelLabel: string +} -- 2.51.2 From c7d2e7acc2a459bf128ecef579d54af0f4ef9754 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 21 Mar 2024 14:23:56 -0400 Subject: [PATCH 27/34] feat(ui): Use ansi-to-react for log coloring Vastly simplifies log formatting on the dashboard --- package-lock.json | 35 +++++++++++++++++++++++++++++++---- package.json | 3 ++- src/backend/common/logging.ts | 4 ++-- src/client/index.css | 17 ++++++++++++----- src/client/logs/LogLine.tsx | 20 ++------------------ 5 files changed, 49 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc94c1c7..b882e88e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,12 @@ "dependencies": { "@astronautlabs/mdns": "^1.0.7", "@awaitjs/express": "^0.6.3", + "@curvenote/ansi-to-react": "^7.0.0", "@fortawesome/fontawesome-svg-core": "^6.4.2", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@foxxmd/chromecast-client": "^1.0.4", - "@foxxmd/logging": "^0.1.11", + "@foxxmd/logging": "^0.1.12", "@foxxmd/string-sameness": "^0.4.0", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", @@ -1067,6 +1068,22 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@curvenote/ansi-to-react": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@curvenote/ansi-to-react/-/ansi-to-react-7.0.0.tgz", + "integrity": "sha512-+m4V86QPmaZ7udMp7Yg81A31dLBGO8gglkPGWPzUJNxLCSyOrJ04pQmdZQelNBEA7MSGz8wf+6RHcuEaujdhHw==", + "dependencies": { + "anser": "^2.1.1", + "escape-carriage": "^1.3.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": "^16.3.2 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.3.2 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@dbus-types/dbus": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@dbus-types/dbus/-/dbus-0.0.4.tgz", @@ -1626,9 +1643,9 @@ } }, "node_modules/@foxxmd/logging": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@foxxmd/logging/-/logging-0.1.11.tgz", - "integrity": "sha512-k0qdUFvtyzqlrCDxaXNJCjpKCzfa8jdspArhE/pXd5Lnv69jJvwNVFIwgbfm+Yhrr7rwY+y5/gxSpZKZc19reg==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@foxxmd/logging/-/logging-0.1.12.tgz", + "integrity": "sha512-vLu0Ip+eDY9k7X7PmefY+Aj6gDwjN/MWJp4icW/Op4By6vZrSJV6eJyrYX9hoXppBUYOH0us09tqGhwSYLtQxA==", "dependencies": { "pino": "^8.19.0", "pino-abstract-transport": "^1.1.0", @@ -3342,6 +3359,11 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/anser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/anser/-/anser-2.1.1.tgz", + "integrity": "sha512-nqLm4HxOTpeLOxcmB3QWmV5TcDFhW9y/fyQ+hivtDFcK4OQ+pQ5fzPnXHM1Mfcm0VkLtvVi1TCPr++Qy0Q/3EQ==" + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -4822,6 +4844,11 @@ "node": ">=6" } }, + "node_modules/escape-carriage": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz", + "integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==" + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", diff --git a/package.json b/package.json index b9fe2b2e..6d37e855 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,12 @@ "dependencies": { "@astronautlabs/mdns": "^1.0.7", "@awaitjs/express": "^0.6.3", + "@curvenote/ansi-to-react": "^7.0.0", "@fortawesome/fontawesome-svg-core": "^6.4.2", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@foxxmd/chromecast-client": "^1.0.4", - "@foxxmd/logging": "^0.1.11", + "@foxxmd/logging": "^0.1.12", "@foxxmd/string-sameness": "^0.4.0", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", diff --git a/src/backend/common/logging.ts b/src/backend/common/logging.ts index ec13c566..8df2e147 100644 --- a/src/backend/common/logging.ts +++ b/src/backend/common/logging.ts @@ -21,7 +21,7 @@ export const initLogger = (): [FoxLogger, Transform] => { const stream = new PassThrough({objectMode: true}); const logger = buildLogger('debug', [ buildDestinationStdout(opts.console), - buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: false}) + buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: true}) ]); return [logger, stream]; } @@ -32,7 +32,7 @@ export const appLogger = async (config?: FoxLogOptions): Promise<[FoxLogger, Pas const logger = await loggerAppRolling(config, { logBaseDir: typeof process.env.CONFIG_DIR === 'string' ? process.env.CONFIG_DIR : undefined, destinations: [ - buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: false}) + buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: true}) ] }); return [logger, stream]; diff --git a/src/client/index.css b/src/client/index.css index d3d207f7..12ce6185 100644 --- a/src/client/index.css +++ b/src/client/index.css @@ -30,18 +30,25 @@ a { .dark .loading { fill: white; } -.blue { +.blue,.ansi-blue-fg { color: rgb(97, 175, 239) } -.red { +.red,.ansi-red-fg { color: rgb(255 123 133); } -.green { +.green,.ansi-green-fg { color: rgb(170 255 109); } -.purple { +.purple,.ansi-magenta-fg { color: rgb(224 124 253); } -.yellow { +.yellow,.ansi-yellow-fg { color: rgb(253 198 94); } +.ansi-cyan-fg { + color: rgb(255, 255, 255) +} + +.ansi-bright-black-fg { + color: rgb(169, 169, 169) +} diff --git a/src/client/logs/LogLine.tsx b/src/client/logs/LogLine.tsx index 8c2360bc..ddb33512 100644 --- a/src/client/logs/LogLine.tsx +++ b/src/client/logs/LogLine.tsx @@ -1,25 +1,9 @@ import React, {PropsWithChildren} from 'react'; -import {parseLogLine} from "../utils/index"; +import Ansi from "@curvenote/ansi-to-react"; -const getClass = (level: string) => { - switch(level) { - case 'debug': - return 'debug blue'; - case 'warn': - return 'warn yellow'; - case 'info': - return 'info green'; - case 'verbose': - return 'verbose purple'; - case 'error': - return 'error red'; - } -} const LogLine = (props: PropsWithChildren<{level: number, levelLabel: string, message: string}>) => { - const lineParts = parseLogLine(props.message); - const levelClass = getClass(props.levelLabel); return ( -
{lineParts.timestamp} {props.levelLabel.padEnd(7, ' ')} : {lineParts.message}
+
{props.message}
) }; -- 2.51.2 From f79413391573f5986771a7ba5f479c560ac6f053 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 21 Mar 2024 15:30:33 -0400 Subject: [PATCH 28/34] fix: Fix missing parent logger --- src/backend/scrobblers/LastfmScrobbler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/scrobblers/LastfmScrobbler.ts b/src/backend/scrobblers/LastfmScrobbler.ts index 16465a68..8ba0a867 100644 --- a/src/backend/scrobblers/LastfmScrobbler.ts +++ b/src/backend/scrobblers/LastfmScrobbler.ts @@ -33,7 +33,7 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { constructor(name: any, config: LastfmClientConfig, options = {}, notifier: Notifiers, emitter: EventEmitter, logger: Logger) { super('lastfm', name, config, notifier, emitter, logger); // @ts-expect-error sloppy data structure assign - this.api = new LastfmApiClient(name, config.data, options) + this.api = new LastfmApiClient(name, config.data, {...options, logger}) } formatPlayObj = (obj: any, options: FormatPlayObjectOptions = {}) => LastfmApiClient.formatPlayObj(obj, options); -- 2.51.2 From d86dd63236ee4e4a7bac13e8a20d3fad995f9c2f Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 21 Mar 2024 15:50:43 -0400 Subject: [PATCH 29/34] fix: Fix log name to match existing logs names --- src/backend/common/logging.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/backend/common/logging.ts b/src/backend/common/logging.ts index 8df2e147..f912d5da 100644 --- a/src/backend/common/logging.ts +++ b/src/backend/common/logging.ts @@ -6,7 +6,7 @@ import { loggerAppRolling, LogOptions as FoxLogOptions, Logger as FoxLogger, - childLogger, + childLogger, LogLevel, } from '@foxxmd/logging'; import {PassThrough, Transform} from "node:stream"; import {buildLogger, buildDestinationStdout, buildDestinationJsonPrettyStream} from "@foxxmd/logging/factory"; @@ -26,8 +26,14 @@ export const initLogger = (): [FoxLogger, Transform] => { return [logger, stream]; } -export const appLogger = async (config?: FoxLogOptions): Promise<[FoxLogger, PassThrough]> => { +export const appLogger = async (config: FoxLogOptions = {}): Promise<[FoxLogger, PassThrough]> => { const stream = new PassThrough({objectMode: true}); + if(process.env.LOG_PATH === undefined && (config.file === undefined || config.file !== false) && (typeof config.file !== 'object' || config.file?.path === undefined)) { + config.file = { + level: config.file as LogLevel, + path: 'logs/scrobble.log' + } + } const opts = parseLogOptions(config) const logger = await loggerAppRolling(config, { logBaseDir: typeof process.env.CONFIG_DIR === 'string' ? process.env.CONFIG_DIR : undefined, -- 2.51.2 From 2f51e7ad99b9fd2ba6df00197b36c4bd7e7bfed4 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 21 Mar 2024 16:29:33 -0400 Subject: [PATCH 30/34] feat: Enable colorizing docker log output --- Dockerfile | 1 + src/backend/common/logging.ts | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 48fc92d4..c6b54476 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,6 +59,7 @@ COPY --from=base /usr/local/lib /usr/local/lib ENV NODE_ENV=production ENV IS_DOCKER=true +ENV COLORED_CONSOLE=true # #RUN yarn global add patch-package \ # && yarn install --production=true \ diff --git a/src/backend/common/logging.ts b/src/backend/common/logging.ts index f912d5da..69480875 100644 --- a/src/backend/common/logging.ts +++ b/src/backend/common/logging.ts @@ -6,21 +6,36 @@ import { loggerAppRolling, LogOptions as FoxLogOptions, Logger as FoxLogger, - childLogger, LogLevel, + childLogger, LogLevel, PrettyOptionsExtra, } from '@foxxmd/logging'; import {PassThrough, Transform} from "node:stream"; import {buildLogger, buildDestinationStdout, buildDestinationJsonPrettyStream} from "@foxxmd/logging/factory"; +import {parseBool} from "../utils.js"; export let logPath = path.resolve(projectDir, `./logs`); if (typeof process.env.CONFIG_DIR === 'string') { logPath = path.resolve(process.env.CONFIG_DIR, './logs'); } +// docker stdout only colorizes if run with `-it` flag or `tty: true` in docker-compose +// but most common outputs and web log viewers (portainer, dozzle) support colors and using those flags/options is not common for most users +// so +// set COLORED_CONSOLE=true in Dockerfile to coerce colorizing output when running in our docker container. +// and using this instead of FORCE_COLOR (used by colorette) so that we only affect console output instead of all streams +const coloredEnv = process.env.COLORED_CONSOLE; +const coloredConsole = (coloredEnv === undefined || coloredEnv === '') ? undefined : parseBool(process.env.COLORED_CONSOLE); +const prettyDefaults: PrettyOptionsExtra = {}; +// colorette only does autodetection if `colorize` prop is not present *at all*, rather than just being undefined +// so need to use default object and only add if we detect there is a non-empty value +if(coloredConsole !== undefined) { + prettyDefaults.colorize = coloredConsole; +} + export const initLogger = (): [FoxLogger, Transform] => { const opts = parseLogOptions({file: false, console: 'debug'}) const stream = new PassThrough({objectMode: true}); const logger = buildLogger('debug', [ - buildDestinationStdout(opts.console), + buildDestinationStdout(opts.console, prettyDefaults), buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: true}) ]); return [logger, stream]; @@ -39,7 +54,8 @@ export const appLogger = async (config: FoxLogOptions = {}): Promise<[FoxLogger, logBaseDir: typeof process.env.CONFIG_DIR === 'string' ? process.env.CONFIG_DIR : undefined, destinations: [ buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: true}) - ] + ], + pretty: prettyDefaults }); return [logger, stream]; } -- 2.51.2 From 6c27406803d155568e02e2489471f196fd358798 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 21 Mar 2024 17:08:14 -0400 Subject: [PATCH 31/34] fix: Fix colored output being applied to file logging --- Dockerfile | 2 +- package-lock.json | 10 +++++----- package.json | 2 +- src/backend/common/logging.ts | 19 ++----------------- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/Dockerfile b/Dockerfile index c6b54476..1029bd0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,7 +59,7 @@ COPY --from=base /usr/local/lib /usr/local/lib ENV NODE_ENV=production ENV IS_DOCKER=true -ENV COLORED_CONSOLE=true +ENV COLORED_STD=true # #RUN yarn global add patch-package \ # && yarn install --production=true \ diff --git a/package-lock.json b/package-lock.json index b882e88e..f3dc96af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@foxxmd/chromecast-client": "^1.0.4", - "@foxxmd/logging": "^0.1.12", + "@foxxmd/logging": "^0.1.13", "@foxxmd/string-sameness": "^0.4.0", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", @@ -126,7 +126,7 @@ }, "../foxxmd/logging": { "name": "@foxxmd/logging", - "version": "0.1.11", + "version": "0.1.13", "extraneous": true, "license": "MIT", "dependencies": { @@ -1643,9 +1643,9 @@ } }, "node_modules/@foxxmd/logging": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@foxxmd/logging/-/logging-0.1.12.tgz", - "integrity": "sha512-vLu0Ip+eDY9k7X7PmefY+Aj6gDwjN/MWJp4icW/Op4By6vZrSJV6eJyrYX9hoXppBUYOH0us09tqGhwSYLtQxA==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@foxxmd/logging/-/logging-0.1.13.tgz", + "integrity": "sha512-BEw20UxCQxvKdw76uIEPq2fpvr9rquC8tjp8SVeD6pTy9M7AIVpHwbeRv8+Kk2pDgEoJogkIIJrFCbPIcqwa9Q==", "dependencies": { "pino": "^8.19.0", "pino-abstract-transport": "^1.1.0", diff --git a/package.json b/package.json index 6d37e855..acb8c160 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@foxxmd/chromecast-client": "^1.0.4", - "@foxxmd/logging": "^0.1.12", + "@foxxmd/logging": "^0.1.13", "@foxxmd/string-sameness": "^0.4.0", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", diff --git a/src/backend/common/logging.ts b/src/backend/common/logging.ts index 69480875..6b58639d 100644 --- a/src/backend/common/logging.ts +++ b/src/backend/common/logging.ts @@ -17,25 +17,11 @@ if (typeof process.env.CONFIG_DIR === 'string') { logPath = path.resolve(process.env.CONFIG_DIR, './logs'); } -// docker stdout only colorizes if run with `-it` flag or `tty: true` in docker-compose -// but most common outputs and web log viewers (portainer, dozzle) support colors and using those flags/options is not common for most users -// so -// set COLORED_CONSOLE=true in Dockerfile to coerce colorizing output when running in our docker container. -// and using this instead of FORCE_COLOR (used by colorette) so that we only affect console output instead of all streams -const coloredEnv = process.env.COLORED_CONSOLE; -const coloredConsole = (coloredEnv === undefined || coloredEnv === '') ? undefined : parseBool(process.env.COLORED_CONSOLE); -const prettyDefaults: PrettyOptionsExtra = {}; -// colorette only does autodetection if `colorize` prop is not present *at all*, rather than just being undefined -// so need to use default object and only add if we detect there is a non-empty value -if(coloredConsole !== undefined) { - prettyDefaults.colorize = coloredConsole; -} - export const initLogger = (): [FoxLogger, Transform] => { const opts = parseLogOptions({file: false, console: 'debug'}) const stream = new PassThrough({objectMode: true}); const logger = buildLogger('debug', [ - buildDestinationStdout(opts.console, prettyDefaults), + buildDestinationStdout(opts.console), buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: true}) ]); return [logger, stream]; @@ -54,8 +40,7 @@ export const appLogger = async (config: FoxLogOptions = {}): Promise<[FoxLogger, logBaseDir: typeof process.env.CONFIG_DIR === 'string' ? process.env.CONFIG_DIR : undefined, destinations: [ buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: true}) - ], - pretty: prettyDefaults + ] }); return [logger, stream]; } -- 2.51.2 From 64f222b5b74d143f619591e8f920cb0255d5726d Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 25 Mar 2024 09:49:01 -0400 Subject: [PATCH 32/34] fix: Add binary alias(?) to pass appstream guidelines flathub/io.github.foxxmd.multiscrobbler#14 --- flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml b/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml index 4c603dc8..f130b8f4 100644 --- a/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml +++ b/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml @@ -3,6 +3,7 @@ io.github.foxxmd.multiscrobbler io.github.foxxmd.multiscrobbler.desktop + multiscrobbler io.github.foxxmd.multiscrobbler.desktop multi-scrobbler -- 2.51.2 From 9ea71b931c787fbf9fd575ef1d183ab664431ae8 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Tue, 26 Mar 2024 09:04:31 -0400 Subject: [PATCH 33/34] fix(flatpak): Fix missing package.json needed to run as esm module --- flatpak/io.github.foxxmd.multiscrobbler.yml | 17 +- package-lock.json | 2751 ++++++++++--------- src/backend/utils/StringUtils.ts | 3 +- 3 files changed, 1481 insertions(+), 1290 deletions(-) diff --git a/flatpak/io.github.foxxmd.multiscrobbler.yml b/flatpak/io.github.foxxmd.multiscrobbler.yml index cfeae422..a3fe0585 100644 --- a/flatpak/io.github.foxxmd.multiscrobbler.yml +++ b/flatpak/io.github.foxxmd.multiscrobbler.yml @@ -30,7 +30,7 @@ modules: - mkdir -p /app/bin /app/lib /app/lib/dist /app/lib/src /app/lib/assets - cp -a /usr/lib/sdk/node18/bin/{node,npm} /app/bin - cp -a /usr/lib/sdk/node18/lib/* /app/lib - - rm -r /app/lib/node_modules/npm/{docs,man} + - rm -r /app/lib/node_modules/npm/{docs,man} # remove this when updating to newer runtime-version # remove dev dependencies - npm prune --production @@ -38,6 +38,10 @@ modules: # copy node_modules needed to run app - cp -r node_modules/. /app/lib/node_modules + # even if not using package.json for scripts it must be present + # so that tsx/node runs as esm (needs to see "type": "module" in file) + - cp package.json /app/lib/package.json + # copy app files to runtime dir - cp -r dist/. /app/lib/dist - cp -r src/. /app/lib/src @@ -59,11 +63,12 @@ modules: subdir: main sources: # use for official releases - - type: git - url: https://github.com/FoxxMD/multi-scrobbler - tag: 0.6.3 - commit: 4bd996eb466137ffc5c3f48305afabde03720525 - dest: main +# - type: git +# url: https://github.com/FoxxMD/multi-scrobbler +# tag: 0.6.3 +# commit: 4bd996eb466137ffc5c3f48305afabde03720525 +# dest: main + # use if developing locally # - type: dir # path: /home/yourUser/multi-scrobbler diff --git a/package-lock.json b/package-lock.json index f3dc96af..2a0c20dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -124,47 +124,6 @@ "npm": ">=9.1.0" } }, - "../foxxmd/logging": { - "name": "@foxxmd/logging", - "version": "0.1.13", - "extraneous": true, - "license": "MIT", - "dependencies": { - "pino": "^8.19.0", - "pino-abstract-transport": "^1.1.0", - "pino-pretty": "^11.0.0", - "pino-roll": "^1.0.1", - "pump": "^3.0.0" - }, - "devDependencies": { - "@types/chai": "^4.3.0", - "@types/chai-as-promised": "^7.1.5", - "@types/dateformat": "^5.0.2", - "@types/mocha": "^9.1.0", - "@types/node": "^18.0.0", - "@types/pump": "^1.1.3", - "chai": "^4.3.6", - "chai-as-promised": "^7.1.1", - "dateformat": "^5.0.3", - "mocha": "^10.2.0", - "p-event": "^6.0.0", - "sinon": "^17.0.1", - "sinon-chai": "^3.7.0", - "ts-essentials": "^9.4.1", - "tshy": "^1.7.0", - "tsx": "^4.7.1", - "typedoc": "^0.25.11", - "typedoc-plugin-inline-sources": "^1.0.2", - "typedoc-plugin-missing-exports": "^2.2.0", - "typedoc-plugin-replace-text": "^3.3.0", - "typescript": "^5.3.3", - "with-local-tmp-dir": "^5.1.1" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=9.3.0" - } - }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -192,13 +151,13 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -221,114 +180,43 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz", + "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz", + "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.1", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/helpers": "^7.24.1", + "@babel/parser": "^7.24.1", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -344,14 +232,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", + "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -387,9 +275,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz", - "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz", + "integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -397,7 +285,7 @@ "@babel/helper-function-name": "^7.23.0", "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" @@ -456,12 +344,12 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -499,22 +387,22 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { @@ -561,9 +449,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -588,28 +476,29 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", + "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", "dev": true, "dependencies": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -687,9 +576,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -699,12 +588,12 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz", - "integrity": "sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz", + "integrity": "sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -714,12 +603,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -753,12 +642,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -768,13 +657,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", - "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", + "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -784,13 +673,13 @@ } }, "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz", - "integrity": "sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz", + "integrity": "sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-flow": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-flow": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -800,13 +689,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", "dev": true, "dependencies": { "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-simple-access": "^7.22.5" }, "engines": { @@ -817,12 +706,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", - "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", + "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -833,12 +722,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", - "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz", + "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, @@ -850,13 +739,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", - "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", + "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -866,12 +755,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", - "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.1.tgz", + "integrity": "sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -881,12 +770,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", - "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz", + "integrity": "sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -896,15 +785,15 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", - "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.1.tgz", + "integrity": "sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.23.3" + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-typescript": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -914,14 +803,14 @@ } }, "node_modules/@babel/preset-flow": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.23.3.tgz", - "integrity": "sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.24.1.tgz", + "integrity": "sha512-sWCV2G9pcqZf+JHyv/RyqEIpFypxdCSxWIxQjpdaQxenNog7cN1pr76hg8u0Fz8Qgg0H4ETkGcJnXL8d4j0PPA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-transform-flow-strip-types": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-transform-flow-strip-types": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -931,16 +820,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", - "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", + "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-typescript": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-syntax-jsx": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-typescript": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -969,9 +858,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -980,33 +869,33 @@ } }, "node_modules/@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1015,9 +904,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", @@ -1037,6 +926,15 @@ "cookie": "^0.5.0" } }, + "node_modules/@bundled-es-modules/cookie/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@bundled-es-modules/statuses": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", @@ -1506,16 +1404,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -1537,18 +1425,6 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -1562,18 +1438,18 @@ } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@faker-js/faker": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.0.tgz", - "integrity": "sha512-htW87352wzUCdX1jyUQocUcmAaFqcR/w082EC8iP/gtkF0K+aKcBp0hR5Arb7dzR8tQ1TrhE9DNa5EbJELm84w==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", + "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", "dev": true, "funding": [ { @@ -1698,28 +1574,6 @@ "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1739,6 +1593,62 @@ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, + "node_modules/@inquirer/confirm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.0.tgz", + "integrity": "sha512-nH5mxoTEoqk6WpoBz80GMpDSm9jH5V9AF8n+JZAZfMzd9gHeEG9w1o3KawPRR72lfzpP+QxBHLkOKLEApwhDiQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^7.1.0", + "@inquirer/type": "^1.2.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-7.1.0.tgz", + "integrity": "sha512-FRCiDiU54XHt5B/D8hX4twwZuzSP244ANHbu3R7CAsJfiv1dUOz24ePBgCZjygEjDUi6BWIJuk4eWLKJ7LATUw==", + "dev": true, + "dependencies": { + "@inquirer/type": "^1.2.1", + "@types/mute-stream": "^0.0.4", + "@types/node": "^20.11.26", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "cli-spinners": "^2.9.2", + "cli-width": "^4.1.0", + "figures": "^3.2.0", + "mute-stream": "^1.0.0", + "run-async": "^3.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/@types/node": { + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@inquirer/type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.2.1.tgz", + "integrity": "sha512-xwMfkPAxeo8Ji/IxfUSqzRi0/+F2GIqJmpc5/thelgMGsjNZcjDDRBO9TLXT1s/hdx/mK5QbVIvgoLIFgXhTMQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1925,6 +1835,16 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@istanbuljs/nyc-config-typescript": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz", @@ -1950,30 +1870,30 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } @@ -1984,9 +1904,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -2007,9 +1927,9 @@ } }, "node_modules/@mswjs/interceptors": { - "version": "0.25.15", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.25.15.tgz", - "integrity": "sha512-s4jdyxmq1eeftfDXJ7MUiK/jlvYaU8Sr75+42hHCVBrYez0k51RHbMitKIKdmsF92Q6gwhp8Sm1MmvdA9llpcg==", + "version": "0.25.16", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.25.16.tgz", + "integrity": "sha512-8QC8JyKztvoGAdPgyZy49c9vSHHAZjHagwl4RY9E8carULk8ym3iTaiawrT1YoLF/qb449h48f71XDPgkUSOUg==", "dev": true, "dependencies": { "@open-draft/deferred-promise": "^2.2.0", @@ -2172,17 +2092,17 @@ } }, "node_modules/@remix-run/router": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz", - "integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", "engines": { "node": ">=14.0.0" } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", - "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", + "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", "cpu": [ "arm" ], @@ -2193,9 +2113,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", - "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", + "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", "cpu": [ "arm64" ], @@ -2206,9 +2126,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", - "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", + "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", "cpu": [ "arm64" ], @@ -2219,9 +2139,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", - "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", + "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", "cpu": [ "x64" ], @@ -2232,9 +2152,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", - "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", + "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", "cpu": [ "arm" ], @@ -2245,9 +2165,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", - "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", + "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", "cpu": [ "arm64" ], @@ -2258,9 +2178,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", - "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", + "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", "cpu": [ "arm64" ], @@ -2271,9 +2191,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", - "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", + "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", "cpu": [ "riscv64" ], @@ -2284,9 +2204,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", - "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", + "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", "cpu": [ "x64" ], @@ -2297,9 +2217,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", - "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", + "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", "cpu": [ "x64" ], @@ -2310,9 +2230,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", - "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", + "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", "cpu": [ "arm64" ], @@ -2323,9 +2243,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", - "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", + "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", "cpu": [ "ia32" ], @@ -2336,9 +2256,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", - "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", + "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", "cpu": [ "x64" ], @@ -2360,9 +2280,9 @@ } }, "node_modules/@supercharge/promise-pool": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@supercharge/promise-pool/-/promise-pool-3.1.1.tgz", - "integrity": "sha512-TgCm6jVqMPv+OgD5uBNND/CkCwNDdXPQlcprtnXsWSBpTCy0q5CI6vRj+jsUiXE1xeRaKIX4UeaYJqzZBL92sg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@supercharge/promise-pool/-/promise-pool-3.2.0.tgz", + "integrity": "sha512-pj0cAALblTZBPtMltWOlZTQSLT07jIaFNeM8TWoJD1cQMgDB9mcMlVMoetiB35OzNJpqQ2b+QEtwiR9f20mADg==", "engines": { "node": ">=8" } @@ -2487,9 +2407,9 @@ } }, "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.10.tgz", + "integrity": "sha512-PiaIWIoPvO6qm6t114ropMCagj6YAF24j9OkCA2mJDXFnlionEwhsBCJ8yek4aib575BI3OkART/90WsgHgLWw==", "devOptional": true }, "node_modules/@tsconfig/node12": { @@ -2585,9 +2505,9 @@ } }, "node_modules/@types/chai": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", - "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz", + "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==", "dev": true }, "node_modules/@types/chai-as-promised": { @@ -2659,9 +2579,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.42", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.42.tgz", - "integrity": "sha512-ckM3jm2bf/MfB3+spLPWYPUH573plBFwpOhqQ2WottxYV85j1HQFlxmnTq57X1yHY9awZPig06hL/cLMgNWHIQ==", + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", "dev": true, "dependencies": { "@types/node": "*", @@ -2671,9 +2591,9 @@ } }, "node_modules/@types/express-session": { - "version": "1.17.10", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.10.tgz", - "integrity": "sha512-U32bC/s0ejXijw5MAzyaV4tuZopCh/K7fPoUDyNbsRXHvPSeymygYD1RFL99YOLhF5PNOkzswvOTRaVHdL1zMw==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.0.tgz", + "integrity": "sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA==", "dev": true, "dependencies": { "@types/express": "*" @@ -2759,10 +2679,19 @@ "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", "dev": true }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { - "version": "18.19.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.11.tgz", - "integrity": "sha512-hzdHPKpDdp5bEcRq1XTlZ2ntVjLcHCTV73dEcGg02eSY/+9AZ+jlfz6i00+zOrunMWenjHuI49J8J7Y9uz50JQ==", + "version": "18.19.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.26.tgz", + "integrity": "sha512-+wiMJsIwLOYCvUqSdKTrfkS8mpTp+MPINe6+Np4TAGFWWRWiBQ5kSq9nZGCSPkzx9mvT+uEukzpX4MOSCydcvw==", "dependencies": { "undici-types": "~5.26.4" } @@ -2783,14 +2712,14 @@ } }, "node_modules/@types/prop-types": { - "version": "15.7.11", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/qs": { - "version": "6.9.11", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", - "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", "dev": true }, "node_modules/@types/range-parser": { @@ -2800,9 +2729,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.2.48", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", - "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", + "version": "18.2.70", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.70.tgz", + "integrity": "sha512-hjlM2hho2vqklPhopNkXkdkeq6Lv8WSZTpr7956zY+3WS5cfYUewtCzsJLsbW5dEv3lfSeQ4W14ZFeKC437JRQ==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2810,9 +2739,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", - "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.22.tgz", + "integrity": "sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==", "devOptional": true, "dependencies": { "@types/react": "*" @@ -2841,9 +2770,9 @@ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@types/semver": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", - "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/send": { @@ -2883,9 +2812,9 @@ } }, "node_modules/@types/statuses": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.4.tgz", - "integrity": "sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", + "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", "dev": true }, "node_modules/@types/superagent": { @@ -2912,6 +2841,12 @@ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "node_modules/@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", @@ -2930,16 +2865,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.1.tgz", - "integrity": "sha512-OLvgeBv3vXlnnJGIAgCLYKjgMEU+wBGj07MQ/nxAaON+3mLzX7mJbhRYrVGiVvFiXtwFlkcBa/TtmglHy0UbzQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz", + "integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.0.1", - "@typescript-eslint/type-utils": "7.0.1", - "@typescript-eslint/utils": "7.0.1", - "@typescript-eslint/visitor-keys": "7.0.1", + "@typescript-eslint/scope-manager": "7.3.1", + "@typescript-eslint/type-utils": "7.3.1", + "@typescript-eslint/utils": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2948,7 +2883,7 @@ "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2998,19 +2933,19 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.1.tgz", - "integrity": "sha512-8GcRRZNzaHxKzBPU3tKtFNing571/GwPBeCvmAUw0yBtfE2XVd0zFKJIMSWkHJcPQi0ekxjIts6L/rrZq5cxGQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz", + "integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.0.1", - "@typescript-eslint/types": "7.0.1", - "@typescript-eslint/typescript-estree": "7.0.1", - "@typescript-eslint/visitor-keys": "7.0.1", + "@typescript-eslint/scope-manager": "7.3.1", + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3026,16 +2961,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.1.tgz", - "integrity": "sha512-v7/T7As10g3bcWOOPAcbnMDuvctHzCFYCG/8R4bK4iYzdFqsZTbXGln0cZNVcwQcwewsYU2BJLay8j0/4zOk4w==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", + "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.0.1", - "@typescript-eslint/visitor-keys": "7.0.1" + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3043,18 +2978,18 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.1.tgz", - "integrity": "sha512-YtT9UcstTG5Yqy4xtLiClm1ZpM/pWVGFnkAa90UfdkkZsR1eP2mR/1jbHeYp8Ay1l1JHPyGvoUYR6o3On5Nhmw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz", + "integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.0.1", - "@typescript-eslint/utils": "7.0.1", + "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/utils": "7.3.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3070,12 +3005,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.1.tgz", - "integrity": "sha512-uJDfmirz4FHib6ENju/7cz9SdMSkeVvJDK3VcMFvf/hAShg8C74FW+06MaQPODHfDJp/z/zHfgawIJRjlu0RLg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", + "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3083,13 +3018,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.1.tgz", - "integrity": "sha512-SO9wHb6ph0/FN5OJxH4MiPscGah5wjOd0RRpaLvuBv9g8565Fgu0uMySFEPqwPHiQU90yzJ2FjRYKGrAhS1xig==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", + "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.0.1", - "@typescript-eslint/visitor-keys": "7.0.1", + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3098,7 +3033,7 @@ "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3110,6 +3045,15 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3159,21 +3103,21 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.1.tgz", - "integrity": "sha512-oe4his30JgPbnv+9Vef1h48jm0S6ft4mNwi9wj7bX10joGn07QRfqIqFHoMiajrtoU88cIhXf8ahwgrcbNLgPA==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", + "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.0.1", - "@typescript-eslint/types": "7.0.1", - "@typescript-eslint/typescript-estree": "7.0.1", + "@typescript-eslint/scope-manager": "7.3.1", + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/typescript-estree": "7.3.1", "semver": "^7.5.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3217,16 +3161,16 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.1.tgz", - "integrity": "sha512-hwAgrOyk++RTXrP4KzCg7zB2U0xt7RUU0ZdMSCsqF3eKUwkdXUMyTb0qdCuji7VIbcpG62kKTU9M1J1c9UpFBw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", + "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/types": "7.3.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3486,13 +3430,16 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3517,19 +3464,6 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, - "node_modules/assert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" - } - }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -3573,9 +3507,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.17", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", - "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "funding": [ { "type": "opencollective", @@ -3591,8 +3525,8 @@ } ], "dependencies": { - "browserslist": "^4.22.2", - "caniuse-lite": "^1.0.30001578", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -3626,11 +3560,14 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, - "engines": { + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { "node": ">= 0.4" }, "funding": { @@ -3698,22 +3635,14 @@ } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "engines": { "node": ">=8" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/body-parser": { @@ -3753,11 +3682,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -3778,9 +3708,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "funding": [ { "type": "opencollective", @@ -3796,8 +3726,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -3809,10 +3739,9 @@ } }, "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -3829,7 +3758,7 @@ ], "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "ieee754": "^1.2.1" } }, "node_modules/buffer-from": { @@ -3906,6 +3835,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/caching-transform/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "peer": true + }, "node_modules/caching-transform/node_modules/write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", @@ -3920,13 +3856,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3960,9 +3901,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001581", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz", - "integrity": "sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==", + "version": "1.0.30001600", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", "funding": [ { "type": "opencollective", @@ -4032,12 +3973,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, "node_modules/charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", @@ -4084,6 +4019,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -4108,18 +4054,6 @@ "node": ">=6" } }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cli-spinners": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", @@ -4133,12 +4067,12 @@ } }, "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/cliui": { @@ -4328,9 +4262,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -4351,6 +4285,14 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "devOptional": true }, + "node_modules/croner": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/croner/-/croner-7.0.7.tgz", + "integrity": "sha512-05wALDHKjt9zG1JbpziNnWPCwwv9fUKbNf6q0dWaDMJ/eDxW0394Q2R1VAzKvDgoEZBT9FhWSHHFIcgwLgXjcQ==", + "engines": { + "node": ">=6.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4533,27 +4475,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defaults/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -4563,16 +4484,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -4734,9 +4658,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.652", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.652.tgz", - "integrity": "sha512-XvQaa8hVUAuEJtLw6VKQqvdOxTOfBLWfI10t2xWpezx4XXD3k8bdLweEKeItqaa0+OkJX5l0mP1W+JWobyIDrg==" + "version": "1.4.715", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz", + "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -4759,6 +4683,25 @@ "once": "^1.4.0" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -4837,9 +4780,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "engines": { "node": ">=6" } @@ -4867,16 +4810,16 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -4922,9 +4865,9 @@ } }, "node_modules/eslint-plugin-prefer-arrow-functions": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow-functions/-/eslint-plugin-prefer-arrow-functions-3.2.4.tgz", - "integrity": "sha512-HbPmlbO/iYQeVs2fuShNkGVJDfVfgSd84Vzxv+xlh+nIVoSsZvTj6yOqszw4mtG9JbiqMShVWqbVeoVsejE59w==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow-functions/-/eslint-plugin-prefer-arrow-functions-3.3.2.tgz", + "integrity": "sha512-XRGsga9cK6pZ48IA2PM2PABBlWshRYhkofkQxcWzCM0YlDnal2hrQKsuz0FqtBHimJpgEXGgHUko3KrOayxlOQ==", "dev": true, "peerDependencies": { "eslint": ">=5.0.0" @@ -4974,28 +4917,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/eslint/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -5017,18 +4938,6 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -5138,16 +5047,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -5196,14 +5105,6 @@ "node": ">= 0.8.0" } }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express-session/node_modules/cookie-signature": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", @@ -5222,29 +5123,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5258,34 +5136,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", @@ -5319,6 +5169,17 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -5345,9 +5206,9 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "node_modules/fastq": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", - "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dependencies": { "reusify": "^1.0.4" } @@ -5499,24 +5360,24 @@ } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/flow-parser": { - "version": "0.227.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.227.0.tgz", - "integrity": "sha512-nOygtGKcX/siZK/lFzpfdHEfOkfGcTW7rNroR1Zsz6T/JxSahPALXVt5qVHq/fgvMJuv096BTKbgxN3PzVBaDA==", + "version": "0.231.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.231.0.tgz", + "integrity": "sha512-WVzuqwq7ZnvBceCG0DGeTQebZE+iIU0mlk5PmJgYj9DDrt+0isGC2m1ezW9vxL4V+HERJJo9ExppOnwKH2op6Q==", "dev": true, "engines": { "node": ">=0.4.0" } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -5555,6 +5416,13 @@ "node": ">=8.0.0" } }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "peer": true + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -5708,15 +5576,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5746,9 +5618,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -5757,53 +5629,54 @@ } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=10" } }, "node_modules/globals": { @@ -5929,20 +5802,20 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -5962,12 +5835,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -6004,9 +5877,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -6024,9 +5897,9 @@ } }, "node_modules/headers-polyfill": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.2.tgz", - "integrity": "sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", "dev": true }, "node_modules/help-me": { @@ -6157,15 +6030,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -6198,39 +6062,13 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -6263,14 +6101,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6383,21 +6223,6 @@ "node": ">=8" } }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6409,33 +6234,11 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, "engines": { "node": ">= 0.4" }, @@ -6519,21 +6322,27 @@ } }, "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6582,21 +6391,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.11" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -6617,22 +6411,28 @@ } }, "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6788,9 +6588,9 @@ } }, "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "peer": true, "dependencies": { @@ -6826,9 +6626,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "peer": true, "dependencies": { @@ -6971,9 +6771,9 @@ } }, "node_modules/jscodeshift": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.15.1.tgz", - "integrity": "sha512-hIJfxUy8Rt4HkJn/zZPU9ChKfKZM1342waJ1QC2e2YsPcWhM+3BJ4dcfQCzArTrk1jJeNLB341H+qOcEHRxJZg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.15.2.tgz", + "integrity": "sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==", "dev": true, "dependencies": { "@babel/core": "^7.23.0", @@ -7022,15 +6822,15 @@ } }, "node_modules/jscodeshift/node_modules/recast": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.4.tgz", - "integrity": "sha512-qtEDqIZGVcSZCHniWwZWbRy79Dc6Wp3kT/UmDA2RJKBPg7+7k51aQBZirHmUGn5uvHf2rg8DkjizrN26k61ATw==", + "version": "0.23.6", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.6.tgz", + "integrity": "sha512-9FHoNjX1yjuesMwuthAmPKabxYQdOgihFYmT5ebXfYGBcnqXZf3WOVz+5foEZ8Y83P4ZY6yQD5GMmtV+pgCCAQ==", "dev": true, "dependencies": { - "assert": "^2.0.0", "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" }, "engines": { @@ -7449,15 +7249,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/mimic-response": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", @@ -7479,15 +7270,14 @@ } }, "node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" } }, "node_modules/minimist": { @@ -7499,11 +7289,11 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/mitt": { @@ -7546,23 +7336,25 @@ "node": ">= 14.0.0" } }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, "node_modules/mocha/node_modules/ms": { @@ -7604,24 +7396,23 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/msw": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.1.5.tgz", - "integrity": "sha512-r39AZk4taMmUEYwtzDAgFy38feqJy1yaKykvo0QE8q7H7c28yH/WIlOmE7oatjkC3dMgpTYfND8MaxeywgU+Yg==", + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.2.10.tgz", + "integrity": "sha512-OQhHBocUsI8j+czCTRouGCGYE8pk6hq8HQ0HFg9mYQg7KCzqVpUSbMikmRbRXGoid28FFvYqjbxB3/UWw50VZQ==", "dev": true, "hasInstallScript": true, "dependencies": { "@bundled-es-modules/cookie": "^2.0.0", "@bundled-es-modules/statuses": "^1.0.1", + "@inquirer/confirm": "^3.0.0", "@mswjs/cookies": "^1.1.0", - "@mswjs/interceptors": "^0.25.15", + "@mswjs/interceptors": "^0.25.16", "@open-draft/until": "^2.1.0", "@types/cookie": "^0.6.0", "@types/statuses": "^2.0.4", "chalk": "^4.1.2", - "chokidar": "^3.4.2", "graphql": "^16.8.1", "headers-polyfill": "^4.0.2", - "inquirer": "^8.2.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.2", "path-to-regexp": "^6.2.0", @@ -7636,11 +7427,10 @@ "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mswjs" + "url": "https://github.com/sponsors/mswjs" }, "peerDependencies": { - "typescript": ">= 4.7.x <= 5.3.x" + "typescript": ">= 4.7.x" }, "peerDependenciesMeta": { "typescript": { @@ -7669,9 +7459,9 @@ "dev": true }, "node_modules/msw/node_modules/type-fest": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz", - "integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.14.0.tgz", + "integrity": "sha512-on5/Cw89wwqGZQu+yWO0gGMGu8VNxsaW9SB2HE8yJjllEk7IDTwnSN1dUVldYILhYPN5HzD7WAaw2cc/jBfn0Q==", "dev": true, "engines": { "node": ">=16" @@ -7725,10 +7515,13 @@ } }, "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/mz": { "version": "2.7.0", @@ -7798,28 +7591,6 @@ "node": ">= 0.10.5" } }, - "node_modules/node-dir/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/node-dir/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -7839,9 +7610,9 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/nodemon": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.3.tgz", - "integrity": "sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", + "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", "dev": true, "dependencies": { "chokidar": "^3.5.2", @@ -7866,16 +7637,6 @@ "url": "https://opencollective.com/nodemon" } }, - "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/nodemon/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -7897,22 +7658,10 @@ "node": ">=10" } }, - "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/nodemon/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -8088,6 +7837,27 @@ "node": ">=8" } }, + "node_modules/nyc/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/nyc/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -8159,6 +7929,23 @@ "node": ">=8" } }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "peer": true + }, "node_modules/nyc/node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", @@ -8204,9 +7991,9 @@ } }, "node_modules/oauth": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", - "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.0.tgz", + "integrity": "sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==" }, "node_modules/object-assign": { "version": "4.1.1", @@ -8233,13 +8020,13 @@ } }, "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -8309,21 +8096,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/open": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", @@ -8356,29 +8128,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -8548,12 +8297,12 @@ } }, "node_modules/passport-oauth2": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.7.0.tgz", - "integrity": "sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", "dependencies": { "base64url": "3.x.x", - "oauth": "0.9.x", + "oauth": "0.10.x", "passport-strategy": "1.x.x", "uid2": "0.0.x", "utils-merge": "1.x.x" @@ -8603,6 +8352,25 @@ "npm": ">5" } }, + "node_modules/patch-package/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/patch-package/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -8626,9 +8394,9 @@ } }, "node_modules/patch-package/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -8786,29 +8554,6 @@ "split2": "^4.0.0" } }, - "node_modules/pino-abstract-transport/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/pino-abstract-transport/node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", @@ -8848,29 +8593,6 @@ "pino-pretty": "bin.js" } }, - "node_modules/pino-pretty/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/pino-pretty/node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", @@ -8996,10 +8718,19 @@ "node": ">=12.0.0" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -9017,7 +8748,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -9092,11 +8823,14 @@ } }, "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/postcss-nested": { @@ -9118,9 +8852,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -9434,11 +9168,11 @@ } }, "node_modules/react-router": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz", - "integrity": "sha512-a0H638ZXULv1OdkmiK6s6itNhoy33ywxmUFT/xtSoVyf9VnC7n7+VT4LjVzdIHSaF5TIh9ylUgxMXksHTgGrKg==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", "dependencies": { - "@remix-run/router": "1.14.2" + "@remix-run/router": "1.15.3" }, "engines": { "node": ">=14.0.0" @@ -9448,12 +9182,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.3.tgz", - "integrity": "sha512-kNzubk7n4YHSrErzjLK72j0B5i969GsuCGazRl3G6j1zqZBLjuSlYBdVdkDOgzGdPIffUOc9nmgiadTEVoq91g==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", "dependencies": { - "@remix-run/router": "1.14.2", - "react-router": "6.21.3" + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" }, "engines": { "node": ">=14.0.0" @@ -9561,14 +9295,15 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -9636,13 +9371,12 @@ } }, "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "peer": true, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/resolve-pkg-maps": { @@ -9664,19 +9398,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -9701,10 +9422,30 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", + "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -9717,19 +9458,19 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.13.0", + "@rollup/rollup-android-arm64": "4.13.0", + "@rollup/rollup-darwin-arm64": "4.13.0", + "@rollup/rollup-darwin-x64": "4.13.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", + "@rollup/rollup-linux-arm64-gnu": "4.13.0", + "@rollup/rollup-linux-arm64-musl": "4.13.0", + "@rollup/rollup-linux-riscv64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-musl": "4.13.0", + "@rollup/rollup-win32-arm64-msvc": "4.13.0", + "@rollup/rollup-win32-ia32-msvc": "4.13.0", + "@rollup/rollup-win32-x64-msvc": "4.13.0", "fsevents": "~2.3.2" } }, @@ -9756,9 +9497,9 @@ } }, "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", "dev": true, "engines": { "node": ">=0.12.0" @@ -9786,15 +9527,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -9825,9 +9557,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.70.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", - "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz", + "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -9939,29 +9671,31 @@ "peer": true }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -10028,23 +9762,32 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/simple-update-notifier": { "version": "2.0.0", @@ -10071,9 +9814,9 @@ } }, "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -10117,9 +9860,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -10168,6 +9911,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/spawn-wrap/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "peer": true + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -10334,6 +10084,14 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -10392,17 +10150,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sucrase/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/superagent": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", @@ -10446,9 +10193,9 @@ } }, "node_modules/superagent/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -10522,17 +10269,6 @@ "node": ">=14.0.0" } }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/temp": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", @@ -10545,6 +10281,26 @@ "node": ">=6.0.0" } }, + "node_modules/temp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/temp/node_modules/rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -10572,28 +10328,25 @@ "node": ">=8" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "peer": true, "dependencies": { - "brace-expansion": "^1.1.7" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/text-table": { @@ -10634,6 +10387,12 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -10674,9 +10433,12 @@ } }, "node_modules/toad-scheduler": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/toad-scheduler/-/toad-scheduler-3.0.0.tgz", - "integrity": "sha512-BYmrORvaGmjziir/ra8qD3qAKczJveqK5ZOO/wn7oS5qLI4iibOza3DBGgu+EwNp9zdlBTKSeZPmxZxzGy1dxw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/toad-scheduler/-/toad-scheduler-3.0.1.tgz", + "integrity": "sha512-UunrUeXWHXNauNMP47OHKfFeJlHxt89f29fdwey+EQLnVGbGPcFk7ArTuTk7SCcrSeVDTlhr5ayFHal0Z1LhJg==", + "dependencies": { + "croner": "^7.0.5" + } }, "node_modules/toidentifier": { "version": "1.0.1", @@ -10699,9 +10461,9 @@ } }, "node_modules/ts-api-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", - "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { "node": ">=16" @@ -10794,9 +10556,9 @@ "dev": true }, "node_modules/tsx": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.0.tgz", - "integrity": "sha512-I+t79RYPlEYlHn9a+KzwrvEwhJg35h/1zHsLC2JXvhC2mdynMv6Zxzvhv5EMV6VF5qJlLlkSnMVvdZV3PSIGcg==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz", + "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==", "dependencies": { "esbuild": "~0.19.10", "get-tsconfig": "^4.7.2" @@ -10871,9 +10633,9 @@ } }, "node_modules/typedoc": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.7.tgz", - "integrity": "sha512-m6A6JjQRg39p2ZVRIN3NKXgrN8vzlHhOS+r9ymUYtcUP/TIQPvWSq7YgE5ZjASfv5Vd5BW5xrir6Gm2XNNcOow==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.12.tgz", + "integrity": "sha512-F+qhkK2VoTweDXd1c42GS/By2DvI2uDF4/EpG424dTexSHdtCH52C6IcAvMA6jR3DzAWZjHpUOW+E02kyPNUNw==", "dev": true, "dependencies": { "lunr": "^2.3.9", @@ -10888,7 +10650,16 @@ "node": ">= 16" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/typedoc/node_modules/minimatch": { @@ -10907,9 +10678,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -10920,16 +10691,16 @@ } }, "node_modules/typescript-eslint": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.0.1.tgz", - "integrity": "sha512-aIquOfwHkGHrMSH57HxLT+1Qzp99YtGxEHXMRD+BXOc8fkuFBbA5BXsMYnoVXFuXOWBdXg8U2rN9Xe4p7LrPSQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.3.1.tgz", + "integrity": "sha512-psqcnHPRCdVIDbgj6RvfpwUKqMcNxIw7eizgxYi46X2BmXK6LxYqPD+SbDfPuA9JW+yPItY6aKJLRNbW7lZ4rA==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "7.0.1", - "@typescript-eslint/parser": "7.0.1" + "@typescript-eslint/eslint-plugin": "7.3.1", + "@typescript-eslint/parser": "7.3.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -10964,9 +10735,9 @@ } }, "node_modules/typescript-json-schema/node_modules/@types/node": { - "version": "16.18.77", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.77.tgz", - "integrity": "sha512-zwqAbRkHjGlxH9PBv8i9dmeaDpBRgfQDSFuREMF2Z+WUi8uc13gfRquMV/8LxBqwm+7jBz+doTVkEEA1CIWOnQ==", + "version": "16.18.91", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.91.tgz", + "integrity": "sha512-h8Q4klc8xzc9kJKr7UYNtJde5TU2qEePVyH3WyzJaUC+3ptyc5kPQbWOIUcn8ZsG5+KSkq+P0py0kC0VqxgAXw==", "dev": true }, "node_modules/typescript-json-schema/node_modules/cliui": { @@ -10983,6 +10754,26 @@ "node": ">=12" } }, + "node_modules/typescript-json-schema/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript-json-schema/node_modules/safe-stable-stringify": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", @@ -11137,19 +10928,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -11194,14 +10972,14 @@ } }, "node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", + "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" + "esbuild": "^0.20.1", + "postcss": "^8.4.36", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -11256,27 +11034,424 @@ "picocolors": "^1.0.0" } }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -11308,15 +11483,18 @@ } }, "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11330,16 +11508,16 @@ "peer": true }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -11401,6 +11579,12 @@ "signal-exit": "^3.0.2" } }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", @@ -11457,9 +11641,12 @@ "dev": true }, "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } diff --git a/src/backend/utils/StringUtils.ts b/src/backend/utils/StringUtils.ts index bb468a98..e10eed15 100644 --- a/src/backend/utils/StringUtils.ts +++ b/src/backend/utils/StringUtils.ts @@ -1,8 +1,7 @@ import { DELIMITERS } from "../common/infrastructure/Atomic.js"; import { parseRegexSingleOrFail } from "../utils.js"; import { PlayObject } from "../../core/Atomic.js"; -import {stringSameness, StringSamenessResult} from "@foxxmd/string-sameness"; -import {strategies} from '@foxxmd/string-sameness'; +import {stringSameness, StringSamenessResult, strategies} from "@foxxmd/string-sameness"; const {levenStrategy, diceStrategy} = strategies; -- 2.51.2 From 41900b00487edfd8901de0408e17d38119c6b262 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Tue, 26 Mar 2024 09:36:37 -0400 Subject: [PATCH 34/34] chore: Bump version for release --- flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml | 1 + package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml b/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml index f130b8f4..ed8767db 100644 --- a/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml +++ b/flatpak/io.github.foxxmd.multiscrobbler.metainfo.xml @@ -49,6 +49,7 @@ + diff --git a/package-lock.json b/package-lock.json index 2a0c20dc..6196f9a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "multi-scrobbler", - "version": "0.6.5", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "multi-scrobbler", - "version": "0.6.5", + "version": "0.7.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index acb8c160..c40063d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "multi-scrobbler", - "version": "0.6.5", + "version": "0.7.0", "type": "module", "description": "scrobble plays from multiple sources to multiple clients", "scripts": { -- 2.51.2