diff --git a/package-lock.json b/package-lock.json index 5468d6cc..a2a4fd33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,7 @@ "http-cookie-agent": "^7.0.1", "icecast-metadata-stats": "^0.1.12", "iso-websocket": "^0.3.0", - "iti": "^0.6.0", + "iti": "^0.7.0", "json5": "^2.2.3", "keyv": "^5.5.0", "kodi-api": "^0.2.1", @@ -6988,9 +6988,10 @@ } }, "node_modules/iti": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/iti/-/iti-0.6.0.tgz", - "integrity": "sha512-JqujcnAIF3pmzitjbT3acc0LkordU6oHBDvWeT6a25wvEVBddFX3DFx/p6YBwGX1TTFsyLgVZtwhGOknthC96A==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iti/-/iti-0.7.0.tgz", + "integrity": "sha512-ERAkSZdhgrYCKF7zqX7PEGjWpjvotP858ACtpRhXssOo+vdYPD97UUn+/Tzif7oKiabdefQ4NzDETXGHZhZ7dA==", + "license": "MIT", "dependencies": { "utility-types": "^3.10.0" }, diff --git a/package.json b/package.json index d6693ace..af071362 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "http-cookie-agent": "^7.0.1", "icecast-metadata-stats": "^0.1.12", "iso-websocket": "^0.3.0", - "iti": "^0.6.0", + "iti": "^0.7.0", "json5": "^2.2.3", "keyv": "^5.5.0", "kodi-api": "^0.2.1", diff --git a/src/backend/index.ts b/src/backend/index.ts index d6abdb3a..3e47477a 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -20,6 +20,7 @@ import { isDebugMode, parseBool, readJson, retry, sleep } from "./utils.js"; import { createVegaGenerator } from './utils/SchemaUtils.js'; import ScrobbleClients from './scrobblers/ScrobbleClients.js'; import ScrobbleSources from './sources/ScrobbleSources.js'; +import { Notifiers } from './notifier/Notifiers.js'; dayjs.extend(utc) dayjs.extend(isBetween); @@ -95,7 +96,14 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`) createVegaGenerator() initLogger.info('Schema definitions generated'); - initServer(logger, appLoggerStream, output); + const scrobbleClients = new ScrobbleClients(root.get('clientEmitter'), root.get('sourceEmitter'), root.get('localUrl'), root.get('configDir'), root.get('logger')); + const scrobbleSources = new ScrobbleSources(root.get('sourceEmitter'), { + localUrl: root.get('localUrl'), + configDir: root.get('configDir'), + version: root.get('version') + }, root.get('logger')); + + initServer(logger, appLoggerStream, output, scrobbleSources, scrobbleClients); 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'); @@ -106,18 +114,16 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`) logger.warn(appConfigFail); } - const notifiers = root.get('notifiers'); + const notifiers = new Notifiers(root.get('notifierEmitter'), root.get('clientEmitter'), root.get('sourceEmitter'), root.get('logger')); //root.get('notifiers'); await notifiers.buildWebhooks(webhooks); /* * setup clients * */ - const scrobbleClients = root.get('clients') as ScrobbleClients; await scrobbleClients.buildClientsFromConfig(notifiers); /* * setup sources * */ - const scrobbleSources = root.get('sources') as ScrobbleSources; await scrobbleSources.buildSourcesFromConfig([]); // check ambiguous client/source types like this for now diff --git a/src/backend/ioc.ts b/src/backend/ioc.ts index 3536f922..ff412106 100644 --- a/src/backend/ioc.ts +++ b/src/backend/ioc.ts @@ -5,9 +5,6 @@ import { createContainer } from "iti"; import path from "path"; import { projectDir } from "./common/index.js"; import { WildcardEmitter } from "./common/WildcardEmitter.js"; -import { Notifiers } from "./notifier/Notifiers.js"; -import ScrobbleClients from "./scrobblers/ScrobbleClients.js"; -import ScrobbleSources from "./sources/ScrobbleSources.js"; import { generateBaseURL } from "./utils/NetworkUtils.js"; import { PassThrough } from "stream"; @@ -22,7 +19,7 @@ let root: ReturnType; export interface RootOptions { baseUrl?: string, - port?: string | number + port?: number logger: Logger disableWeb?: boolean loggerStream?: PassThrough @@ -51,23 +48,24 @@ const createRoot = (options?: RootOptions) => { const f = e; }); + const portVal: number | string = process.env.PORT ?? port; + return createContainer().add({ version, configDir: configDir, isProd: process.env.NODE_ENV !== undefined && (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'prod'), - port: process.env.PORT ?? port, + // @ts-ignore + port: (Number.isInteger(portVal) ? portVal : Number.parseInt(portVal)) as number, disableWeb, clientEmitter: () => cEmitter, sourceEmitter: () => sEmitter, notifierEmitter: () => new EventEmitter(), loggerStream, loggingConfig, + logger: options.logger }).add((items) => { const localUrl = generateBaseURL(baseUrl, items.port) return { - clients: () => new ScrobbleClients(items.clientEmitter, items.sourceEmitter, localUrl, items.configDir, options.logger), - sources: () => new ScrobbleSources(items.sourceEmitter, { localUrl, configDir: items.configDir, version }, options.logger), - notifiers: () => new Notifiers(items.notifierEmitter, items.clientEmitter, items.sourceEmitter, options.logger), localUrl, hasDefinedBaseUrl: baseUrl !== undefined, isSubPath: localUrl.pathname !== '/' && localUrl.pathname.length > 0 diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index cace9c89..0612c18e 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -12,6 +12,7 @@ import { LogOutputConfig, PlayObject, SOURCE_SOT, + SOURCE_SOT_TYPES, SourceStatusData, } from "../../core/Atomic.js"; import { capitalize } from "../../core/StringUtils.js"; @@ -30,6 +31,8 @@ import { makeClientCheckMiddle, makeSourceCheckMiddle } from "./middleware.js"; import { setupPlexRoutes } from "./plexRoutes.js"; import { setupTautulliRoutes } from "./tautulliRoutes.js"; import { setupWebscrobblerRoutes } from "./webscrobblerRoutes.js"; +import ScrobbleSources from "../sources/ScrobbleSources.js"; +import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; const maxBufferSize = 300; const output: Record> = {}; @@ -51,7 +54,7 @@ const getLogs = (minLevel: number, limit: number = maxBufferSize, sort: 'asc' | return allLogs.flat(1).sort((a, b) => a.time - b.time).slice(0, limit); } -export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: PassThrough, initialLogOutput: LogDataPretty[] = []) => { +export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: PassThrough, initialLogOutput: LogDataPretty[] = [], scrobbleSources: ScrobbleSources, scrobbleClients: ScrobbleClients) => { for(const level of Object.keys(logger.levels.labels)) { output[level] = new FixedSizeList(maxBufferSize); } @@ -90,9 +93,6 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: } }); - const scrobbleSources = root.get('sources'); - const scrobbleClients = root.get('clients'); - const clientMiddleFunc = makeClientCheckMiddle(scrobbleClients); const sourceMiddleFunc = makeSourceCheckMiddle(scrobbleSources); @@ -169,9 +169,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: app.getAsync('/api/status', async (req, res, next) => { - const ss = root.get('sources'); - - const sourceData = ss.sources.map((x) => { + const sourceData = scrobbleSources.sources.map((x) => { const { type, tracksDiscovered = 0, @@ -193,7 +191,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: hasAuthInteraction: requiresAuthInteraction, authed, players: 'players' in x ? (x as MemorySource).playersToObject() : {}, - sot: ('playerSourceOfTruth' in x) ? x.playerSourceOfTruth : SOURCE_SOT.HISTORY, + sot: ('playerSourceOfTruth' in x) ? x.playerSourceOfTruth as SOURCE_SOT_TYPES : SOURCE_SOT.HISTORY, supportsUpstreamRecentlyPlayed: x.supportsUpstreamRecentlyPlayed, supportsManualListening: x.supportsManualListening, manualListening: x.manualListening, diff --git a/src/backend/server/index.ts b/src/backend/server/index.ts index 872df819..6de124a6 100644 --- a/src/backend/server/index.ts +++ b/src/backend/server/index.ts @@ -13,11 +13,13 @@ import { getRoot } from "../ioc.js"; import { parseBool } from "../utils.js"; import { getAddress } from "../utils/NetworkUtils.js"; import { setupApi } from "./api.js"; +import ScrobbleSources from '../sources/ScrobbleSources.js'; +import ScrobbleClients from '../scrobblers/ScrobbleClients.js'; const app = addAsync(express()); const router = Router(); -export const initServer = async (parentLogger: Logger, appLoggerStream: PassThrough, initialOutput: LogDataPretty[] = []) => { +export const initServer = async (parentLogger: Logger, appLoggerStream: PassThrough, initialOutput: LogDataPretty[] = [], sources: ScrobbleSources, clients: ScrobbleClients) => { const logger = childLogger(parentLogger, 'API'); // parentLogger.child({labels: ['API']}, mergeArr); @@ -48,7 +50,7 @@ export const initServer = async (parentLogger: Logger, appLoggerStream: PassThro const local = root.get('localUrl'); const localDefined = root.get('hasDefinedBaseUrl'); - setupApi(app, logger, appLoggerStream, initialOutput); + setupApi(app, logger, appLoggerStream, initialOutput, sources, clients); const addy = getAddress(); const addresses: string[] = []; @@ -78,7 +80,7 @@ export const initServer = async (parentLogger: Logger, appLoggerStream: PassThro app.use('/docs', express.static(path.resolve(projectDir, `./docsite/build`))); if(process.env.USE_HASH_ROUTER === undefined) { - process.env.USE_HASH_ROUTER = root.get('isSubPath'); + process.env.USE_HASH_ROUTER = root.get('isSubPath').toString(); } ViteExpress.config({ diff --git a/src/core/Atomic.ts b/src/core/Atomic.ts index 19078bcf..e406133a 100644 --- a/src/core/Atomic.ts +++ b/src/core/Atomic.ts @@ -5,7 +5,31 @@ import { AdditionalTrackInfoResponse } from "../backend/common/vendor/listenbrai export interface SourceStatusData { status: string; - type: "spotify" | "plex" | "tautulli" | "subsonic" | "jellyfin" | "lastfm" | "deezer" | "ytmusic" | "mpris" | "mopidy" | "listenbrainz" | "jriver" | "kodi" | 'webscrobbler' | 'chromecast'; + type: 'spotify' + | 'plex' + | 'tautulli' + | 'subsonic' + | 'jellyfin' + | 'lastfm' + | 'deezer' + | 'endpointlz' + | 'endpointlfm' + | 'ytmusic' + | 'mpris' + | 'mopidy' + | 'musiccast' + | 'listenbrainz' + | 'jriver' + | 'kodi' + | 'webscrobbler' + | 'chromecast' + | 'maloja' + | 'musikcube' + | 'mpd' + | 'vlc' + | 'icecast' + | 'azuracast' + | 'koito'; display: string; tracksDiscovered: number; name: string; @@ -23,7 +47,7 @@ export interface SourceStatusData { export interface ClientStatusData { status: string; - type: "maloja" | "lastfm" | "listenbrainz"; + type: "maloja" | "lastfm" | "listenbrainz" | "koito"; display: string; scrobbled: number; deadLetterScrobbles: number