diff --git a/.gitignore b/.gitignore index 6d7089b..6fcf7a9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ logs/ config/* !config/configuration.js +!config.schema.js credentials.* \ No newline at end of file diff --git a/config/config.schema.js b/config/config.schema.js new file mode 100644 index 0000000..6ab4c25 --- /dev/null +++ b/config/config.schema.js @@ -0,0 +1,110 @@ +const schema = { + type: 'object', + required: [], + properties: { + plex: { + type: 'object', + required: ['url', 'token'], + properties: { + url: { + type: 'string' + }, + token: { + type: 'string' + }, + filters: { + type: 'array', + minItems: 0, + items: { + type: 'object', + properties: { + library: { + type: 'array', + items: { + type: 'string' + }, + minItems: 0, + }, + ip: { + type: 'array', + items: { + type: 'string' + }, + minItems: 0, + }, + deviceId: { + type: 'array', + items: { + type: 'string' + }, + minItems: 0, + }, + platform: { + type: 'array', + items: { + type: 'string' + }, + minItems: 0, + }, + product: { + type: 'array', + items: { + type: 'string' + }, + minItems: 0, + }, + } + } + } + } + }, + + scrobble: { + type: 'object', + properties: { + minimum: { + type: 'object', + properties: { + percent: { + type: 'number' + }, + duration: { + type: 'number' + } + } + } + } + }, + + spotify: { + type: 'object', + required: ['client_id', 'client_secret', 'redirect_uri'], + properties: { + client_id: { + type: 'string' + }, + client_secret: { + type: 'string' + }, + redirect_uri: { + type: 'string' + } + } + }, + + web: { + type: 'object', + required: [], + properties: { + host: { + type: 'string' + }, + port: { + type: 'number' + } + } + } + } +} + +module.exports = schema; \ No newline at end of file diff --git a/config/configuration.js b/config/configuration.js index a72ef17..351250d 100644 --- a/config/configuration.js +++ b/config/configuration.js @@ -1,26 +1,19 @@ -const config = require('config'); +const Ajv = require("ajv"); +const fs = require("fs"); +const logger = require("../services/logging"); +const yaml = require("js-yaml"); -const configuration = { +const configurationBase = { plex: { url: null, - token: null + token: null, + filters: [] // { library, ip, deviceId, platform, product } }, scrobble: { minimum: { percent: null, duration: null }, - plex: { - delay: null, - filters: [] - /* A filter will have the following properties: - library: [""], - ip: [""], - deviceId: [""], - platform: [""], - product: [""] - */ - }, }, spotify: { client_id: null, @@ -33,27 +26,18 @@ const configuration = { } }; -if (config.has("plex.url")) - configuration.plex.url = config.get("plex.url"); -if (config.has("plex.token")) - configuration.plex.token = config.get("plex.token"); +const configurationFile = yaml.load(fs.readFileSync('config/config.yml'), yaml.JSON_SCHEMA); +const configuration = { ...configurationBase, ...configurationFile } -if (config.has("scrobble.plex.delay")) - configuration.scrobble.plex.delay = config.get("scrobble.plex.delay"); -if (config.has("scrobble.plex.filters")) - configuration.scrobble.plex.filters = config.get("scrobble.plex.filters"); +const ajv = new Ajv({ allErrors: true }); +const schema = require("./config.schema"); +const { exit } = require("process"); +const validation = ajv.compile(schema); +const valid = validation(configurationFile); -if (config.has("scrobble.minimum.duration")) - configuration.scrobble.minimum.duration = config.get("scrobble.minimum.duration"); -if (config.has("scrobble.minimum.percent")) - configuration.scrobble.minimum.percent = config.get("scrobble.minimum.percent"); - -if (config.has("spotify")) - configuration.spotify = config.get("spotify"); - -if (config.has("web.host")) - configuration.web.host = config.get("web.host"); -if (config.has("web.port")) - configuration.web.port = config.get("web.port"); +if (!valid) { + logger.error("Configuration is invalid. " + validation.errors.map(e => e.message).join(". ") + "."); + (async () => { await new Promise(resolve => setTimeout(resolve, 1000)); exit(1); })(); +} module.exports = configuration; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ea6ef48..bf35427 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,8 @@ "version": "0.0.0", "license": "ISC", "dependencies": { + "ajv": "^8.17.1", "axios": "^1.7.8", - "config": "^3.3.12", - "dotenv": "^16.4.6", "express": "^4.21.1", "express-rate-limit": "^7.4.1", "helmet": "^8.0.0", @@ -32,6 +31,22 @@ "node": ">= 0.6" } }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -134,18 +149,6 @@ "node": ">= 0.8" } }, - "node_modules/config": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/config/-/config-3.3.12.tgz", - "integrity": "sha512-Vmx389R/QVM3foxqBzXO8t2tUikYZP64Q6vQxGrsMpREeJc/aWRnPRERXWsYzOHAumx/AOoILWe6nU3ZJL+6Sw==", - "license": "MIT", - "dependencies": { - "json5": "^2.2.3" - }, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -236,18 +239,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/dotenv": { - "version": "16.4.6", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.6.tgz", - "integrity": "sha512-JhcR/+KIjkkjiU8yEpaB/USlzVi3i5whwOjpIRNGi9svKEXZSe+Qp6IWAjFjv+2GViAoDRCUv/QLNziQxsLqDg==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -356,6 +347,12 @@ "express": "4 || 5 || ^5.0.0-beta.1" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fast-redact": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", @@ -365,6 +362,12 @@ "node": ">=6" } }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -593,17 +596,11 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/media-typer": { "version": "0.3.0", @@ -844,6 +841,15 @@ "node": ">= 12.13.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index 9a13749..7585280 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,8 @@ "license": "ISC", "description": "", "dependencies": { + "ajv": "^8.17.1", "axios": "^1.7.8", - "config": "^3.3.12", - "dotenv": "^16.4.6", "express": "^4.21.1", "express-rate-limit": "^7.4.1", "helmet": "^8.0.0", diff --git a/services/poll.js b/services/poll.js index ebfbf21..41643db 100644 --- a/services/poll.js +++ b/services/poll.js @@ -78,7 +78,7 @@ function checkIfCanScrobble(current, previous, now) { let filters = []; if (previous.source == 'plex') - filters = config.scrobble.plex.filters; + filters = config.plex.filters; if (!applyFilter(previous, filters)) { logger.debug(previous, 'No filters got triggered. Ignoring.');