From 7e6eddce3e659c3b4d3cdce644cbec7f15492cc3 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 28 Mar 2024 09:04:40 -0400 Subject: [PATCH 01/13] refactor: Reduce usage of ErrorWithCause Error Cause (https://github.com/tc39/proposal-error-cause) was finalized and introduced in ES2022 so we don't need to ponyfill this anymore. Still using the helpful helper functions thought. --- src/backend/common/errors/UpstreamError.ts | 3 +- src/backend/common/vendor/JRiverApiClient.ts | 5 ++-- src/backend/common/vendor/KodiApiClient.ts | 3 +- src/backend/common/vendor/LastfmApiClient.ts | 3 +- .../chromecast/ChromecastClientUtils.ts | 7 ++--- src/backend/index.ts | 5 ++-- .../scrobblers/AbstractScrobbleClient.ts | 10 +++---- src/backend/scrobblers/LastfmScrobbler.ts | 3 +- .../scrobblers/ListenbrainzScrobbler.ts | 3 +- src/backend/scrobblers/MalojaScrobbler.ts | 12 ++++---- src/backend/server/index.ts | 7 ++--- src/backend/sources/AbstractSource.ts | 13 ++++---- src/backend/sources/ChromecastSource.ts | 30 +++++++++---------- src/backend/sources/DeezerSource.ts | 3 +- src/backend/sources/LastfmSource.ts | 7 ++--- src/backend/sources/ListenbrainzSource.ts | 7 ++--- src/backend/sources/MPRISSource.ts | 11 ++++--- src/backend/sources/MopidySource.ts | 3 +- src/backend/sources/SpotifySource.ts | 10 ++----- src/backend/sources/SubsonicSource.ts | 5 +--- src/backend/utils.ts | 14 ++++----- src/backend/utils/MDNSUtils.ts | 9 +++--- 22 files changed, 74 insertions(+), 99 deletions(-) diff --git a/src/backend/common/errors/UpstreamError.ts b/src/backend/common/errors/UpstreamError.ts index e2baae73..452385a3 100644 --- a/src/backend/common/errors/UpstreamError.ts +++ b/src/backend/common/errors/UpstreamError.ts @@ -1,8 +1,7 @@ -import {ErrorWithCause} from "pony-cause"; import { findCauseByFunc } from "../../utils.js"; import {Response} from 'superagent'; -export class UpstreamError extends ErrorWithCause { +export class UpstreamError extends Error { showStopper: boolean = false; response?: Response diff --git a/src/backend/common/vendor/JRiverApiClient.ts b/src/backend/common/vendor/JRiverApiClient.ts index 9868398d..a9d40f9a 100644 --- a/src/backend/common/vendor/JRiverApiClient.ts +++ b/src/backend/common/vendor/JRiverApiClient.ts @@ -2,7 +2,6 @@ import AbstractApiClient from "./AbstractApiClient.js"; import {JRiverData} from "../infrastructure/config/source/jriver.js"; import request, {Request, Response} from 'superagent'; import xml2js from 'xml2js'; -import {ErrorWithCause} from "pony-cause"; import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER} from "../infrastructure/Atomic.js"; const parser = new xml2js.Parser({'async': true}); @@ -134,7 +133,7 @@ export class JRiverApiClient extends AbstractApiClient { this.logger.verbose(`Found ${data.ProgramName} ${data.ProgramVersion} (${data.FriendlyName})`); return true; } catch (e) { - throw new ErrorWithCause('Could not communicate with JRiver server. Verify your server URL is correct.', {cause: e}); + throw new Error('Could not communicate with JRiver server. Verify your server URL is correct.', {cause: e}); } } @@ -152,7 +151,7 @@ export class JRiverApiClient extends AbstractApiClient { if(this.config.username === undefined || this.config.password === undefined) { msg = 'Authentication failed. No username/password was provided in config! Did you mean to do this?'; } - this.logger.error(new ErrorWithCause(msg, {cause: e})); + this.logger.error(new Error(msg, {cause: e})); return false; } } diff --git a/src/backend/common/vendor/KodiApiClient.ts b/src/backend/common/vendor/KodiApiClient.ts index 4f61e6f6..61b63eb8 100644 --- a/src/backend/common/vendor/KodiApiClient.ts +++ b/src/backend/common/vendor/KodiApiClient.ts @@ -1,5 +1,4 @@ import AbstractApiClient from "./AbstractApiClient.js"; -import {ErrorWithCause} from "pony-cause"; import { KodiData } from "../infrastructure/config/source/kodi.js"; import { KodiClient } from 'kodi-api' import normalizeUrl from "normalize-url"; @@ -143,7 +142,7 @@ export class KodiApiClient extends AbstractApiClient { if(this.config.username === undefined || this.config.password === undefined) { msg = 'Authentication failed. No username/password was provided in config! Did you mean to do this?'; } - this.logger.error(new ErrorWithCause(msg, {cause: e})); + this.logger.error(new Error(msg, {cause: e})); return false; } } diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index d31d7c4c..4f0d6595 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -13,7 +13,6 @@ import { LastfmData } from "../infrastructure/config/client/lastfm.js"; import { PlayObject } from "../../../core/Atomic.js"; import {getNodeNetworkException, isNodeNetworkException} from "../errors/NodeErrors.js"; import {nonEmptyStringOrDefault, splitByFirstFound} from "../../../core/StringUtils.js"; -import {ErrorWithCause} from "pony-cause"; import {getScrobbleTsSOCDate} from "../../utils/TimeUtils.js"; import {UpstreamError} from "../errors/UpstreamError.js"; @@ -168,7 +167,7 @@ export default class LastfmApiClient extends AbstractApiClient { } return true; } catch (e) { - throw new ErrorWithCause('Current lastfm credentials file exists but could not be parsed', {cause: e}); + throw new Error('Current lastfm credentials file exists but could not be parsed', {cause: e}); } } diff --git a/src/backend/common/vendor/chromecast/ChromecastClientUtils.ts b/src/backend/common/vendor/chromecast/ChromecastClientUtils.ts index 227726e4..cfcd11a3 100644 --- a/src/backend/common/vendor/chromecast/ChromecastClientUtils.ts +++ b/src/backend/common/vendor/chromecast/ChromecastClientUtils.ts @@ -1,7 +1,6 @@ import { REPORTED_PLAYER_STATUSES, ReportedPlayerStatus } from "../../infrastructure/Atomic.js"; import { PlatformApplication, PlatformType } from "./interfaces.js"; import {Media, MediaController, Result} from "@foxxmd/chromecast-client"; -import {ErrorWithCause} from "pony-cause"; import objectHash from "object-hash"; import { PlayObject } from "../../../../core/Atomic.js"; @@ -27,7 +26,7 @@ export const getCurrentPlatformApplications = async (platform: PlatformType): Pr try { statusRes = await platform.getStatus() } catch (e) { - throw new ErrorWithCause('Unable to fetch platform statuses', {cause: e}); + throw new Error('Unable to fetch platform statuses', {cause: e}); } let status: {applications?: PlatformApplication[]}; @@ -39,7 +38,7 @@ export const getCurrentPlatformApplications = async (platform: PlatformType): Pr } return status.applications; } catch (e) { - throw new ErrorWithCause('Unable to fetch platform statuses', {cause: e}); + throw new Error('Unable to fetch platform statuses', {cause: e}); } } @@ -51,7 +50,7 @@ export const getMediaStatus = async (controller: MediaController.MediaController status = statusRes.unwrapAndThrow(); return status; } catch (e) { - throw new ErrorWithCause('Unable to fetch media status', {cause: e}); + throw new Error('Unable to fetch media status', {cause: e}); } } diff --git a/src/backend/index.ts b/src/backend/index.ts index 559687fd..340bc2e4 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -17,7 +17,6 @@ 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 {loggerDebug, childLogger, LogData, Logger as FoxLogger} from '@foxxmd/logging'; dayjs.extend(utc) @@ -42,7 +41,7 @@ output = output.slice(0, 301); let logger: FoxLogger; process.on('uncaughtExceptionMonitor', (err, origin) => { - const appError = new ErrorWithCause(`Uncaught exception is crashing the app! :( Type: ${origin}`, {cause: err}); + const appError = new Error(`Uncaught exception is crashing the app! :( Type: ${origin}`, {cause: err}); if(logger !== undefined) { logger.error(appError) } else { @@ -149,7 +148,7 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`) logger.info('Scheduler started.'); } catch (e) { - const appError = new ErrorWithCause('Exited with uncaught error', {cause: e}); + const appError = new Error('Exited with uncaught error', {cause: e}); if(logger !== undefined) { logger.error(appError); } else { diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index c8e8d1ca..fc78a737 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -41,7 +41,7 @@ import EventEmitter from "events"; import { compareScrobbleArtists, compareScrobbleTracks, normalizeStr } from "../utils/StringUtils.js"; import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; import {nanoid} from "nanoid"; -import {ErrorWithCause, messageWithCauses} from "pony-cause"; +import {messageWithCauses} from "pony-cause"; import { hasNodeNetworkException } from "../common/errors/NodeErrors.js"; import { comparePlayTemporally, @@ -187,7 +187,7 @@ export default abstract class AbstractScrobbleClient implements Authenticatable // only signal as auth failure if error was NOT either a node network error or a non-showstopping upstream error this.authFailure = !(hasNodeNetworkException(e) || hasUpstreamError(e, false)); this.authed = false; - this.logger.error(new ErrorWithCause(`Authentication test failed!${this.authFailure === false ? ' Due to a network issue. Will retry authentication on next heartbeat.' : ''}`, {cause: e})); + this.logger.error(new Error(`Authentication test failed!${this.authFailure === false ? ' Due to a network issue. Will retry authentication on next heartbeat.' : ''}`, {cause: e})); } } @@ -583,10 +583,10 @@ ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); } catch (e) { if (e instanceof UpstreamError && e.showStopper === false) { this.addDeadLetterScrobble(currQueuedPlay, e); - this.logger.warn(new ErrorWithCause(`Could not scrobble ${buildTrackString(currQueuedPlay.play)} from Source '${currQueuedPlay.source}' but error was not show stopping. Adding scrobble to Dead Letter Queue and will retry on next heartbeat.`, {cause: e})); + this.logger.warn(new Error(`Could not scrobble ${buildTrackString(currQueuedPlay.play)} from Source '${currQueuedPlay.source}' but error was not show stopping. Adding scrobble to Dead Letter Queue and will retry on next heartbeat.`, {cause: e})); } else { this.queuedScrobbles.unshift(currQueuedPlay); - throw new ErrorWithCause('Error occurred while trying to scrobble', {cause: e}); + throw new Error('Error occurred while trying to scrobble', {cause: e}); } } } else if (!timeFrameValid) { @@ -666,7 +666,7 @@ ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); deadScrobble.retries++; deadScrobble.error = messageWithCauses(e); deadScrobble.lastRetry = dayjs(); - this.logger.error(new ErrorWithCause(`Could not scrobble ${buildTrackString(deadScrobble.play)} from Source '${deadScrobble.source}' due to error`, {cause: e})); + this.logger.error(new Error(`Could not scrobble ${buildTrackString(deadScrobble.play)} from Source '${deadScrobble.source}' due to error`, {cause: e})); this.deadLetterScrobbles[deadScrobbleIndex] = deadScrobble; return [false, deadScrobble]; } finally { diff --git a/src/backend/scrobblers/LastfmScrobbler.ts b/src/backend/scrobblers/LastfmScrobbler.ts index 8ba0a867..7f776f0c 100644 --- a/src/backend/scrobblers/LastfmScrobbler.ts +++ b/src/backend/scrobblers/LastfmScrobbler.ts @@ -20,7 +20,6 @@ import EventEmitter from "events"; import { UpstreamError } from "../common/errors/UpstreamError.js"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; import { getScrobbleTsSOCDate } from "../utils/TimeUtils.js"; -import {ErrorWithCause} from "pony-cause"; export default class LastfmScrobbler extends AbstractScrobbleClient { @@ -47,7 +46,7 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { this.logger.info('Initialized'); } catch (e) { this.initialized = false; - this.logger.warn(new ErrorWithCause('Initialization failed', {cause: e})); + this.logger.warn(new Error('Initialization failed', {cause: e})); } return this.initialized; diff --git a/src/backend/scrobblers/ListenbrainzScrobbler.ts b/src/backend/scrobblers/ListenbrainzScrobbler.ts index c6355421..81c7c0a7 100644 --- a/src/backend/scrobblers/ListenbrainzScrobbler.ts +++ b/src/backend/scrobblers/ListenbrainzScrobbler.ts @@ -11,7 +11,6 @@ import { buildTrackString, capitalize } from "../../core/StringUtils.js"; import EventEmitter from "events"; import { UpstreamError } from "../common/errors/UpstreamError.js"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import {ErrorWithCause} from "pony-cause"; export default class ListenbrainzScrobbler extends AbstractScrobbleClient { @@ -40,7 +39,7 @@ export default class ListenbrainzScrobbler extends AbstractScrobbleClient { this.initialized = true; this.logger.info('Initialized'); } catch (e) { - this.logger.warn(new ErrorWithCause('Could not initialize', {cause: e})); + this.logger.warn(new Error('Could not initialize', {cause: e})); this.initialized = false; } } diff --git a/src/backend/scrobblers/MalojaScrobbler.ts b/src/backend/scrobblers/MalojaScrobbler.ts index 9cb27e6e..9d498a96 100644 --- a/src/backend/scrobblers/MalojaScrobbler.ts +++ b/src/backend/scrobblers/MalojaScrobbler.ts @@ -24,9 +24,7 @@ import { buildTrackString, capitalize } from "../../core/StringUtils.js"; import EventEmitter from "events"; import normalizeUrl from "normalize-url"; import { UpstreamError } from "../common/errors/UpstreamError.js"; -import {ErrorWithCause} from "pony-cause"; import { getScrobbleTsSOCDate, getScrobbleTsSOCDateWithContext } from "../utils/TimeUtils.js"; -import e from "express"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; import { isSuperAgentResponseError } from "../common/errors/ErrorUtils.js"; @@ -170,7 +168,7 @@ export default class MalojaScrobbler extends AbstractScrobbleClient { throw new UpstreamError(`API Call failed (HTTP ${status}) => ${message}`, {cause: e}) } } else { - throw new ErrorWithCause('Unexpected error occurred during API call', {cause : e}); + throw new Error('Unexpected error occurred during API call', {cause : e}); } } } @@ -208,7 +206,7 @@ export default class MalojaScrobbler extends AbstractScrobbleClient { } return true; } catch (e) { - this.logger.error(new ErrorWithCause('Communication test failed', {cause: e})); + this.logger.error(new Error('Communication test failed', {cause: e})); return false; } } @@ -244,7 +242,7 @@ export default class MalojaScrobbler extends AbstractScrobbleClient { return [true]; } catch (e) { - this.logger.error(new ErrorWithCause('Unexpected error encountered while testing server health', {cause: e})); + this.logger.error(new Error('Unexpected error encountered while testing server health', {cause: e})); throw e; } } @@ -293,7 +291,7 @@ export default class MalojaScrobbler extends AbstractScrobbleClient { } } catch (e) { if(e instanceof UpstreamError) { - if(e.cause.status === 403) { + if((e?.cause as any)?.status === 403) { // may be an older version that doesn't support auth readiness before db upgrade // and if it was before api was accessible during db build then test would fail during testConnection() if(compareVersions(this.serverVersion, '2.12.19') < 0) { @@ -322,7 +320,7 @@ export default class MalojaScrobbler extends AbstractScrobbleClient { this.serverIsHealthy = true; } } catch (e) { - this.logger.error(new ErrorWithCause(`Testing server health failed due to an unexpected error`, {cause: e})); + this.logger.error(new Error(`Testing server health failed due to an unexpected error`, {cause: e})); this.serverIsHealthy = false; } return this.serverIsHealthy diff --git a/src/backend/server/index.ts b/src/backend/server/index.ts index eb6330a5..4c60232d 100644 --- a/src/backend/server/index.ts +++ b/src/backend/server/index.ts @@ -8,7 +8,6 @@ import { getRoot } from "../ioc.js"; import { setupApi } from "./api.js"; import { getAddress, mergeArr, parseBool } from "../utils.js"; import {stripIndents} from "common-tags"; -import {ErrorWithCause} from "pony-cause"; import {childLogger, LogData, LogDataPretty} from "@foxxmd/logging"; import {PassThrough} from "node:stream"; import {Logger} from '@foxxmd/logging'; @@ -85,13 +84,13 @@ export const initServer = async (parentLogger: Logger, appLoggerStream: PassThro logger.info(`User-defined base URL for UI and redirect URLs (spotify, deezer, lastfm): ${local}`) } }).on('error', (err) => { - throw new ErrorWithCause('Server encountered unrecoverable error', {cause: err}); + throw new Error('Server encountered unrecoverable error', {cause: err}); }); } catch (e) { - throw new ErrorWithCause('Server encountered unrecoverable error', {cause: e}); + throw new Error('Server encountered unrecoverable error', {cause: e}); } } catch (e) { - throw new ErrorWithCause('Server crashed with uncaught exception', {cause: e}); + throw new Error('Server crashed with uncaught exception', {cause: e}); } } diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index 0c9b837c..915f16e6 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -36,7 +36,6 @@ import TupleMap from "../common/TupleMap.js"; import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; import { buildTrackString, capitalize } from "../../core/StringUtils.js"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import {ErrorWithCause} from "pony-cause"; import { comparePlayTemporally, temporalAccuracyIsAtLeast } from "../utils/TimeUtils.js"; export interface RecentlyPlayedOptions { @@ -111,7 +110,7 @@ export default abstract class AbstractSource implements Authenticatable { this.logger.info('Fully Initialized!'); return true; } catch(e) { - this.logger.error(new ErrorWithCause('Initialization failed', {cause: e})); + this.logger.error(new Error('Initialization failed', {cause: e})); return false; } } @@ -135,7 +134,7 @@ export default abstract class AbstractSource implements Authenticatable { this.buildOK = true; } catch (e) { this.buildOK = false; - throw new ErrorWithCause('Building required data for initialization failed', {cause: e}); + throw new Error('Building required data for initialization failed', {cause: e}); } } @@ -166,7 +165,7 @@ export default abstract class AbstractSource implements Authenticatable { this.connectionOK = true; } catch (e) { this.connectionOK = false; - throw new ErrorWithCause('Communicating with upstream service failed', {cause: e}); + throw new Error('Communicating with upstream service failed', {cause: e}); } } @@ -202,7 +201,7 @@ export default abstract class AbstractSource implements Authenticatable { // only signal as auth failure if error was NOT a node network error this.authFailure = findCauseByFunc(e, isNodeNetworkException) === undefined; this.authed = false; - throw new ErrorWithCause(`Authentication test failed!${this.authFailure === false ? ' Due to a network issue. Will retry authentication on next heartbeat.' : ''}`, {cause: e}) + throw new Error(`Authentication test failed!${this.authFailure === false ? ' Due to a network issue. Will retry authentication on next heartbeat.' : ''}`, {cause: e}) } } @@ -322,7 +321,7 @@ export default abstract class AbstractSource implements Authenticatable { try { backlogPlays = await this.getBackloggedPlays(); } catch (e) { - throw new ErrorWithCause('Error occurred while fetching backlogged plays', {cause: e}); + throw new Error('Error occurred while fetching backlogged plays', {cause: e}); } const discovered = this.discover(backlogPlays); @@ -385,7 +384,7 @@ export default abstract class AbstractSource implements Authenticatable { try { await this.processBacklog(); } catch (e) { - this.logger.error(new ErrorWithCause('Cannot start polling because error occurred while processing backlog', {cause: e})); + this.logger.error(new Error('Cannot start polling because error occurred while processing backlog', {cause: e})); this.notify({ title: `${this.identifier} - Polling Error`, message: 'Cannot start polling because error occurred while processing backlog.', diff --git a/src/backend/sources/ChromecastSource.ts b/src/backend/sources/ChromecastSource.ts index c5cef8c6..c0f08dd2 100644 --- a/src/backend/sources/ChromecastSource.ts +++ b/src/backend/sources/ChromecastSource.ts @@ -11,7 +11,7 @@ import { import {EventEmitter} from "events"; import {MediaController, PersistentClient, Media, createPlatform} from "@foxxmd/chromecast-client"; import {Client as CastClient} from 'castv2'; -import {ErrorWithCause, findCauseByReference} from "pony-cause"; +import {findCauseByReference} from "pony-cause"; import { PlayObject } from "../../core/Atomic.js"; import dayjs from "dayjs"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; @@ -140,18 +140,18 @@ export class ChromecastSource extends MemorySource { for (const device of devices) { this.initializeDevice({name: device.name, addresses: [device.address], type: 'googlecast'}).catch((err) => { - this.logger.error(new ErrorWithCause('Uncaught error occurred while connecting to manually configured device', {cause: err})); + this.logger.error(new Error('Uncaught error occurred while connecting to manually configured device', {cause: err})); }); } if (useAutoDiscovery) { if (useAvahi) { this.discoverAvahi(initial).catch((err) => { - this.logger.error(new ErrorWithCause('Uncaught error occurred during mDNS discovery via Avahi', {cause: err})); + this.logger.error(new Error('Uncaught error occurred during mDNS discovery via Avahi', {cause: err})); }); } else { this.discoverNative(initial).catch((err) => { - this.logger.error(new ErrorWithCause('Uncaught error occurred during mDNS discovery', {cause: err})); + this.logger.error(new Error('Uncaught error occurred during mDNS discovery', {cause: err})); }); } } @@ -167,7 +167,7 @@ export class ChromecastSource extends MemorySource { }, }); } catch (e) { - this.logger.error(new ErrorWithCause('Uncaught error occurred during mDNS discovery via Avahi', {cause: e})); + this.logger.error(new Error('Uncaught error occurred during mDNS discovery via Avahi', {cause: e})); } } @@ -181,7 +181,7 @@ export class ChromecastSource extends MemorySource { }, }); } catch (e) { - this.logger.error(new ErrorWithCause('Uncaught error occurred during mDNS discovery', {cause: e})); + this.logger.error(new Error('Uncaught error occurred during mDNS discovery', {cause: e})); } } @@ -261,10 +261,10 @@ export class ChromecastSource extends MemorySource { await client.connect(); } catch (e) { if(index < device.addresses.length - 1) { - this.logger.warn(new ErrorWithCause(`Could not connect to ${device.name} but more interfaces exist, will attempt next host.`, {cause: e})); + this.logger.warn(new Error(`Could not connect to ${device.name} but more interfaces exist, will attempt next host.`, {cause: e})); continue; } else { - throw new ErrorWithCause(`Could not connect to ${device.name} and no additional interfaces exist`, {cause: e}); + throw new Error(`Could not connect to ${device.name} and no additional interfaces exist`, {cause: e}); } } @@ -284,7 +284,7 @@ export class ChromecastSource extends MemorySource { } if(event === "reconnect") { if(payload instanceof Error) { - info.logger.warn(new ErrorWithCause(`Failed to reconnect, will retry ${5 - info.retries} more times`, {cause: e})) + info.logger.warn(new Error(`Failed to reconnect, will retry ${5 - info.retries} more times`, {cause: e})) } else { info.logger.verbose(`Reconnected`); info.retries = 0; @@ -312,13 +312,13 @@ export class ChromecastSource extends MemorySource { break; case 'error': if(info === undefined) { - this.logger.error(new ErrorWithCause(`(${clientName}) Encountered error in castv2 lib`, {cause: payload as Error})); + this.logger.error(new Error(`(${clientName}) Encountered error in castv2 lib`, {cause: payload as Error})); } else { if(NETWORK_ERROR_FAILURE_CODES.some(x => (payload as Error).message.includes(x))) { - info.logger.warn(new ErrorWithCause(`Encountered network error. Will try to reconnect to device`, {cause: payload as Error})); + info.logger.warn(new Error(`Encountered network error. Will try to reconnect to device`, {cause: payload as Error})); info.client.client.close(); } else { - info.logger.error(new ErrorWithCause(`Encountered error in castv2 lib`, {cause: payload as Error})); + info.logger.error(new Error(`Encountered error in castv2 lib`, {cause: payload as Error})); } } break; @@ -336,7 +336,7 @@ export class ChromecastSource extends MemorySource { apps = await getCurrentPlatformApplications(v.platform); v.retries = 0; } catch (e) { - v.logger.warn(new ErrorWithCause(`Could not refresh applications. Will remove after ${5 - v.retries} retries if error does not resolve itself.`, {cause: e})); + v.logger.warn(new Error(`Could not refresh applications. Will remove after ${5 - v.retries} retries if error does not resolve itself.`, {cause: e})); const validationError = findCauseByReference(e, ContextualValidationError); if(validationError && validationError.data !== undefined) { v.logger.warn(JSON.stringify(validationError.data)); @@ -495,7 +495,7 @@ export class ChromecastSource extends MemorySource { try { await this.refreshApplications(); } catch (e) { - this.logger.warn(new ErrorWithCause('Could not refresh all applications', {cause: e})); + this.logger.warn(new Error('Could not refresh all applications', {cause: e})); } for (const [k, v] of this.devices.entries()) { @@ -629,7 +629,7 @@ export class ChromecastSource extends MemorySource { plays.push(playerState); } catch (e) { - application.logger.warn(new ErrorWithCause(`Could not get Player State`, {cause: e})) + application.logger.warn(new Error(`Could not get Player State`, {cause: e})) const validationError = findCauseByReference(e, ContextualValidationError); if (validationError && validationError.data !== undefined) { application.logger.warn(JSON.stringify(validationError.data)); diff --git a/src/backend/sources/DeezerSource.ts b/src/backend/sources/DeezerSource.ts index 9aa941a0..22c3d8db 100644 --- a/src/backend/sources/DeezerSource.ts +++ b/src/backend/sources/DeezerSource.ts @@ -14,7 +14,6 @@ import { DeezerSourceConfig } from "../common/infrastructure/config/source/deeze import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; import EventEmitter from "events"; import { PlayObject } from "../../core/Atomic.js"; -import {ErrorWithCause} from "pony-cause"; export default class DeezerSource extends AbstractSource { workingCredsPath; @@ -102,7 +101,7 @@ export default class DeezerSource extends AbstractSource { this.logger.warn(`No Deezer credentials file found at ${this.workingCredsPath}`); } } catch (e) { - throw new ErrorWithCause('Current deezer credentials file exists but could not be parsed', {cause: e}); + throw new Error('Current deezer credentials file exists but could not be parsed', {cause: e}); } if (this.config.data.accessToken === undefined) { if (this.config.data.clientId === undefined) { diff --git a/src/backend/sources/LastfmSource.ts b/src/backend/sources/LastfmSource.ts index 81a0865a..9dc631a1 100644 --- a/src/backend/sources/LastfmSource.ts +++ b/src/backend/sources/LastfmSource.ts @@ -9,8 +9,7 @@ import MemorySource from "./MemorySource.js"; import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; import dayjs from "dayjs"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import {ErrorWithCause} from "pony-cause"; -import request, {options} from "superagent"; +import request from "superagent"; export default class LastfmSource extends MemorySource { @@ -57,9 +56,9 @@ export default class LastfmSource extends MemorySource { return true; } catch (e) { if(isNodeNetworkException(e)) { - throw new ErrorWithCause('Could not communicate with Last.fm API server', {cause: e}); + throw new Error('Could not communicate with Last.fm API server', {cause: e}); } else if(e.status >= 500) { - throw new ErrorWithCause('Last.fm API server returning an unexpected response', {cause: e}) + throw new Error('Last.fm API server returning an unexpected response', {cause: e}) } return true; } diff --git a/src/backend/sources/ListenbrainzSource.ts b/src/backend/sources/ListenbrainzSource.ts index 9566c156..0ef9b53e 100644 --- a/src/backend/sources/ListenbrainzSource.ts +++ b/src/backend/sources/ListenbrainzSource.ts @@ -4,7 +4,6 @@ import EventEmitter from "events"; import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz.js"; import { ListenbrainzApiClient } from "../common/vendor/ListenbrainzApiClient.js"; import MemorySource from "./MemorySource.js"; -import {ErrorWithCause} from "pony-cause"; import request from "superagent"; import {isNodeNetworkException} from "../common/errors/NodeErrors.js"; import {PlayObject, SOURCE_SOT} from "../../core/Atomic.js"; @@ -42,9 +41,9 @@ export default class ListenbrainzSource extends MemorySource { return true; } catch (e) { if(isNodeNetworkException(e)) { - throw new ErrorWithCause('Could not communicate with Listenbrainz API server', {cause: e}); + throw new Error('Could not communicate with Listenbrainz API server', {cause: e}); } else if(e.status !== 410) { - throw new ErrorWithCause('Listenbrainz API server returning an unexpected response', {cause: e}) + throw new Error('Listenbrainz API server returning an unexpected response', {cause: e}) } return true; } @@ -58,7 +57,7 @@ export default class ListenbrainzSource extends MemorySource { return await this.api.testAuth(); } catch (e) { throw e; - //throw new ErrorWithCause('Could not communicate with Listenbrainz API', {cause: e}); + //throw new Error('Could not communicate with Listenbrainz API', {cause: e}); } } diff --git a/src/backend/sources/MPRISSource.ts b/src/backend/sources/MPRISSource.ts index dff939b2..0ace261c 100644 --- a/src/backend/sources/MPRISSource.ts +++ b/src/backend/sources/MPRISSource.ts @@ -13,7 +13,6 @@ import MemorySource from "./MemorySource.js"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; 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' @@ -96,7 +95,7 @@ export class MPRISSource extends MemorySource { await this.getDBus(); return true; } catch (e) { - throw new ErrorWithCause('Could not get DBus interface from operating system', {cause: e}); + throw new Error('Could not get DBus interface from operating system', {cause: e}); } } @@ -144,7 +143,7 @@ export class MPRISSource extends MemorySource { }); } catch (e) { - this.logger.warn(new ErrorWithCause(`Could not parse D-bus info for player ${plainPlayerName}`, {cause: e})); + this.logger.warn(new Error(`Could not parse D-bus info for player ${plainPlayerName}`, {cause: e})); } } @@ -158,7 +157,7 @@ export class MPRISSource extends MemorySource { // microseconds return dayjs.duration({milliseconds: Number(pos / 1000)}).asSeconds(); } catch(e) { - throw new ErrorWithCause('Could not get player Position', {cause: e}); + throw new Error('Could not get player Position', {cause: e}); } } @@ -167,7 +166,7 @@ export class MPRISSource extends MemorySource { const status = await props['PlaybackStatus']; return status as PlaybackStatus; } catch (e) { - throw new ErrorWithCause('Could not get player PlaybackStatus', {cause: e}) + throw new Error('Could not get player PlaybackStatus', {cause: e}) } } @@ -176,7 +175,7 @@ export class MPRISSource extends MemorySource { const metadata = await props['Metadata']; return this.metadataToPlain(metadata); } catch(e) { - throw new ErrorWithCause('Could not get player Metadata', {cause: e}); + throw new Error('Could not get player Metadata', {cause: e}); } } diff --git a/src/backend/sources/MopidySource.ts b/src/backend/sources/MopidySource.ts index 932e4d48..0215e8ee 100644 --- a/src/backend/sources/MopidySource.ts +++ b/src/backend/sources/MopidySource.ts @@ -15,7 +15,6 @@ import pEvent from 'p-event'; 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 { @@ -118,7 +117,7 @@ export class MopidySource extends MemorySource { return true; } else { this.client.close(); - throw new ErrorWithCause(`Could not connect to Mopidy server`, {cause: (res as Error)}); + throw new Error(`Could not connect to Mopidy server`, {cause: (res as Error)}); } } diff --git a/src/backend/sources/SpotifySource.ts b/src/backend/sources/SpotifySource.ts index dd4098e5..acac7c9e 100644 --- a/src/backend/sources/SpotifySource.ts +++ b/src/backend/sources/SpotifySource.ts @@ -30,7 +30,6 @@ import ArtistObjectSimplified = SpotifyApi.ArtistObjectSimplified; import AlbumObjectSimplified = SpotifyApi.AlbumObjectSimplified; import UserDevice = SpotifyApi.UserDevice; import MemorySource from "./MemorySource.js"; -import {ErrorWithCause} from "pony-cause"; import { PlayObject, SCROBBLE_TS_SOC_END, SCROBBLE_TS_SOC_START, ScrobbleTsSOC } from "../../core/Atomic.js"; import { buildTrackString, truncateStringToLength } from "../../core/StringUtils.js"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; @@ -256,10 +255,10 @@ export default class SpotifySource extends MemorySource { return true; } catch (e) { if(isNodeNetworkException(e)) { - throw new ErrorWithCause('Could not communicate with Spotify API server', {cause: e}); + throw new Error('Could not communicate with Spotify API server', {cause: e}); } if(e.status >= 500) { - throw new ErrorWithCause('Spotify API server returned an unexpected response', { cause: e}); + throw new Error('Spotify API server returned an unexpected response', { cause: e}); } return true; } @@ -277,9 +276,6 @@ export default class SpotifySource extends MemorySource { if(isNodeNetworkException(e)) { this.logger.error('Could not communicate with Spotify API'); } - // this.authFailure = !(e instanceof ErrorWithCause && e.cause !== undefined && isNodeNetworkException(e.cause)); - // this.logger.error(new ErrorWithCause('Could not successfully communicate with Spotify API', {cause: e})); - // this.authed = false; throw e; } } @@ -405,7 +401,7 @@ export default class SpotifySource extends MemorySource { if(hasApiError(e)) { throw new UpstreamError('Error occurred while trying to retrieve current playback state', {cause: e}); } - throw new ErrorWithCause('Error occurred while trying to retrieve current playback state', {cause: e}); + throw new Error('Error occurred while trying to retrieve current playback state', {cause: e}); } } diff --git a/src/backend/sources/SubsonicSource.ts b/src/backend/sources/SubsonicSource.ts index b08f1e19..bee2dc42 100644 --- a/src/backend/sources/SubsonicSource.ts +++ b/src/backend/sources/SubsonicSource.ts @@ -10,11 +10,8 @@ import { RecentlyPlayedOptions } from "./AbstractSource.js"; import EventEmitter from "events"; import { PlayObject } from "../../core/Atomic.js"; 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); @@ -216,7 +213,7 @@ export class SubsonicSource extends MemorySource { } else if(e.status >= 500) { throw new UpstreamError('Subsonic server returning an unexpected response', {cause: e}) } else { - throw new ErrorWithCause('Unexpected error occurred', {cause: e}) + throw new Error('Unexpected error occurred', {cause: e}) } } } diff --git a/src/backend/utils.ts b/src/backend/utils.ts index 724298c4..c15be4bf 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -21,7 +21,7 @@ import { } from "./common/infrastructure/Atomic.js"; import {Request} from "express"; import pathUtil from "path"; -import {ErrorWithCause, getErrorCause} from "pony-cause"; +import {getErrorCause} from "pony-cause"; import backoffStrategies from '@kenyip/backoff-strategies'; import {replaceResultTransformer, stripIndentTransformer, TemplateTag, trimResultTransformer} from 'common-tags'; import {Duration} from "dayjs/plugin/duration.js"; @@ -40,12 +40,12 @@ export async function readJson(this: any, path: any, {throwOnNotFound = true} = const {code} = e; if (code === 'ENOENT') { if (throwOnNotFound) { - throw new ErrorWithCause(`No file found at given path: ${path}`, {cause: e}); + throw new Error(`No file found at given path: ${path}`, {cause: e}); } else { return; } } - throw new ErrorWithCause(`Encountered error while parsing file: ${path}`, {cause: e}) + throw new Error(`Encountered error while parsing file: ${path}`, {cause: e}) } } @@ -536,13 +536,13 @@ export const fileOrDirectoryIsWriteable = (location: string) => { // also can't access directory :( throw new Error(`No ${isDir ? 'directory' : 'file'} exists at ${location} and application does not have permission to write to the parent directory`); } else { - throw new ErrorWithCause(`No ${isDir ? 'directory' : 'file'} exists at ${location} and application is unable to access the parent directory due to a system error`, {cause: accessError}); + throw new Error(`No ${isDir ? 'directory' : 'file'} exists at ${location} and application is unable to access the parent directory due to a system error`, {cause: accessError}); } } } else if(code === 'EACCES') { throw new Error(`${isDir ? 'Directory' : 'File'} exists at ${location} but application does not have permission to write to it.`); } else { - throw new ErrorWithCause(`${isDir ? 'Directory' : 'File'} exists at ${location} but application is unable to access it due to a system error`, {cause: err}); + throw new Error(`${isDir ? 'Directory' : 'File'} exists at ${location} but application is unable to access it due to a system error`, {cause: err}); } } } @@ -600,7 +600,7 @@ export const parseRegexSingleOrFail = (reg: RegExp, val: string): RegExResult | const results = parseRegex(reg, val); if (results !== undefined) { if (results.length > 1) { - throw new ErrorWithCause(`Expected Regex to match once but got ${results.length} results. Either Regex must NOT be global (using 'g' flag) or parsed value must only match regex once. Given: ${val} || Regex: ${reg.toString()}`); + throw new Error(`Expected Regex to match once but got ${results.length} results. Either Regex must NOT be global (using 'g' flag) or parsed value must only match regex once. Given: ${val} || Regex: ${reg.toString()}`); } return results[0]; } @@ -733,7 +733,7 @@ export const getAddress = (host = '0.0.0.0', logger?: Logger): { v4?: string, v6 } catch (e) { if (process.env.DEBUG_MODE === 'true') { if (logger !== undefined) { - logger.warn(new ErrorWithCause('Could not get machine IP address', {cause: e})); + logger.warn(new Error('Could not get machine IP address', {cause: e})); } else { console.warn('Could not get machine IP address'); console.warn(e); diff --git a/src/backend/utils/MDNSUtils.ts b/src/backend/utils/MDNSUtils.ts index 6d25c332..baa31314 100644 --- a/src/backend/utils/MDNSUtils.ts +++ b/src/backend/utils/MDNSUtils.ts @@ -2,7 +2,6 @@ import {Logger} from "@foxxmd/logging"; import AvahiBrowser from 'avahi-browse'; import { MaybeLogger } from "../common/logging.js"; import { sleep } from "../utils.js"; -import {ErrorWithCause} from "pony-cause"; import { MdnsDeviceInfo } from "../common/infrastructure/Atomic.js"; import {Browser, Service, ServiceType} from "@astronautlabs/mdns"; import {debounce, DebouncedFunction} from "./debounce.js"; @@ -78,7 +77,7 @@ export const discoveryAvahi = async (service: string, options?: DiscoveryOptions } }); browser.on(AvahiBrowser.EVENT_DNSSD_ERROR, (err) => { - const e = new ErrorWithCause('Error occurred while using avahi-browse', {cause: err}); + const e = new Error('Error occurred while using avahi-browse', {cause: err}); if (onDnsError) { onDnsError(e) } else { @@ -97,7 +96,7 @@ export const discoveryAvahi = async (service: string, options?: DiscoveryOptions } maybeLogger.debug('Stopped discovery'); } catch (e) { - maybeLogger.warn(new ErrorWithCause('mDNS device discovery with avahi-browse failed', {cause: e})); + maybeLogger.warn(new Error('mDNS device discovery with avahi-browse failed', {cause: e})); } } @@ -121,7 +120,7 @@ export const discoveryNative = async (service: string, options?: DiscoveryOption }) .start(); testBrowser.on('error', (err) => { - maybeLogger.error(new ErrorWithCause('Error occurred during mDNS service discovery', {cause: err})); + maybeLogger.error(new Error('Error occurred during mDNS service discovery', {cause: err})); }); maybeLogger.debug('Waiting 1s to gather advertised mdns services...'); await sleep(1000); @@ -141,7 +140,7 @@ export const discoveryNative = async (service: string, options?: DiscoveryOption } }) browser.on('error', (err) => { - const e = new ErrorWithCause('Error occurred during mDNS discovery', {cause: err}); + const e = new Error('Error occurred during mDNS discovery', {cause: err}); if (onDnsError) { onDnsError(e) } else { -- 2.51.2 From eb62c2cea9eebe8b425651e3824ca6863e349993 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 28 Mar 2024 09:20:33 -0400 Subject: [PATCH 02/13] refactor: optimize imports * Remove unused imports * Normalize spaces in curly braces * Sort by module (npm packages, then relative imports) --- src/backend/common/errors/ErrorUtils.ts | 2 +- src/backend/common/errors/NodeErrors.ts | 2 +- src/backend/common/errors/UpstreamError.ts | 2 +- src/backend/common/infrastructure/Atomic.ts | 12 ++-- .../common/infrastructure/config/aioConfig.ts | 8 +-- .../infrastructure/config/client/clients.ts | 2 +- .../infrastructure/config/client/lastfm.ts | 2 +- .../config/client/listenbrainz.ts | 2 +- .../infrastructure/config/client/maloja.ts | 2 +- .../infrastructure/config/source/jriver.ts | 2 +- .../infrastructure/config/source/kodi.ts | 2 +- .../infrastructure/config/source/lastfm.ts | 2 +- .../config/source/listenbrainz.ts | 2 +- .../infrastructure/config/source/mopidy.ts | 2 +- .../infrastructure/config/source/sources.ts | 20 +++--- .../infrastructure/config/source/spotify.ts | 2 +- .../infrastructure/config/source/subsonic.ts | 2 +- .../infrastructure/config/source/ytmusic.ts | 2 +- .../typings/passport-deezer.d.ts | 3 +- src/backend/common/logging.ts | 18 +++--- .../common/vendor/AbstractApiClient.ts | 4 +- src/backend/common/vendor/JRiverApiClient.ts | 8 +-- src/backend/common/vendor/KodiApiClient.ts | 12 ++-- src/backend/common/vendor/LastfmApiClient.ts | 18 +++--- .../common/vendor/ListenbrainzApiClient.ts | 14 ++--- .../chromecast/ChromecastClientUtils.ts | 6 +- .../common/vendor/chromecast/interfaces.ts | 6 +- .../common/vendor/maloja/interfaces.ts | 2 +- .../common/vendor/subsonic/interfaces.ts | 2 +- src/backend/index.ts | 16 +++-- src/backend/ioc.ts | 18 +++--- .../notifier/AbstractWebhookNotifier.ts | 3 +- src/backend/notifier/GotifyWebhookNotifier.ts | 10 +-- src/backend/notifier/Notifiers.ts | 11 +++- src/backend/notifier/NtfyWebhookNotifier.ts | 10 +-- .../scrobblers/AbstractScrobbleClient.ts | 48 +++++++------- src/backend/scrobblers/LastfmScrobbler.ts | 29 +++------ .../scrobblers/ListenbrainzScrobbler.ts | 18 +++--- src/backend/scrobblers/MalojaScrobbler.ts | 26 ++++---- src/backend/scrobblers/ScrobbleClients.ts | 25 ++++---- src/backend/server/api.ts | 41 ++++++------ src/backend/server/auth.ts | 8 +-- src/backend/server/deezerRoutes.ts | 13 ++-- src/backend/server/index.ts | 17 +++-- src/backend/server/jellyfinRoutes.ts | 8 +-- src/backend/server/middleware.ts | 2 +- src/backend/server/plexRoutes.ts | 9 ++- src/backend/server/tautulliRoutes.ts | 8 +-- src/backend/server/webscrobblerRoutes.ts | 12 ++-- src/backend/sources/AbstractSource.ts | 38 +++++------ src/backend/sources/ChromecastSource.ts | 41 ++++++------ src/backend/sources/DeezerSource.ts | 20 +++--- src/backend/sources/JRiverSource.ts | 12 ++-- src/backend/sources/JellyfinSource.ts | 23 ++++--- src/backend/sources/KodiSource.ts | 8 +-- src/backend/sources/LastfmSource.ts | 18 +++--- src/backend/sources/ListenbrainzSource.ts | 10 +-- src/backend/sources/MPRISSource.ts | 14 ++--- src/backend/sources/MemorySource.ts | 44 ++++++------- src/backend/sources/MopidySource.ts | 22 +++---- .../PlayerState/AbstractPlayerState.ts | 11 ++-- .../sources/PlayerState/GenericPlayerState.ts | 4 +- .../PlayerState/JellyfinPlayerState.ts | 6 +- .../sources/PlayerState/ListenProgress.ts | 2 +- .../sources/PlayerState/ListenRange.ts | 4 +- src/backend/sources/PlexSource.ts | 14 ++--- src/backend/sources/ScrobbleSources.ts | 63 +++++++++---------- src/backend/sources/SpotifySource.ts | 42 ++++++------- src/backend/sources/SubsonicSource.ts | 18 +++--- src/backend/sources/TautulliSource.ts | 10 +-- src/backend/sources/WebScrobblerSource.ts | 14 +++-- src/backend/sources/YTMusicSource.ts | 14 ++--- .../ingressNotifiers/IngressNotifier.ts | 6 +- .../ingressNotifiers/JellyfinNotifier.ts | 8 +-- .../sources/ingressNotifiers/PlexNotifier.ts | 6 +- .../ingressNotifiers/TautulliNotifier.ts | 7 +-- .../ingressNotifiers/WebhookNotifier.ts | 6 +- src/backend/tasks/heartbeatClients.ts | 7 +-- src/backend/tasks/heartbeatSources.ts | 9 ++- src/backend/tests/jellyfin/jellyfin.test.ts | 12 ++-- .../tests/listenbrainz/listenbrainz.test.ts | 26 ++++---- src/backend/tests/player/player.test.ts | 19 +++--- src/backend/tests/scrobbler/TestScrobbler.ts | 8 +-- .../tests/scrobbler/scrobblers.test.ts | 23 ++++--- src/backend/tests/utils/PlayTestUtils.ts | 10 +-- src/backend/tests/utils/networking.ts | 2 +- src/backend/tests/utils/strings.test.ts | 8 +-- src/backend/utils.ts | 29 +++++---- src/backend/utils/MDNSUtils.ts | 8 +-- src/backend/utils/StringUtils.ts | 4 +- src/backend/utils/TimeUtils.ts | 6 +- src/core/Atomic.ts | 6 +- 92 files changed, 563 insertions(+), 594 deletions(-) diff --git a/src/backend/common/errors/ErrorUtils.ts b/src/backend/common/errors/ErrorUtils.ts index 9b7055d4..e8f0f72d 100644 --- a/src/backend/common/errors/ErrorUtils.ts +++ b/src/backend/common/errors/ErrorUtils.ts @@ -1,4 +1,4 @@ -import {ResponseError} from "superagent"; +import { ResponseError } from "superagent"; export const isSuperAgentResponseError = (e: any): e is ResponseError => { return typeof e === 'object' diff --git a/src/backend/common/errors/NodeErrors.ts b/src/backend/common/errors/NodeErrors.ts index d487f2fc..adfe0e61 100644 --- a/src/backend/common/errors/NodeErrors.ts +++ b/src/backend/common/errors/NodeErrors.ts @@ -1,6 +1,6 @@ +import { findCauseByFunc } from "../../utils.js"; import { isArbitraryObject } from "../infrastructure/Atomic.js"; import ErrnoException = NodeJS.ErrnoException; -import { findCauseByFunc } from "../../utils.js"; export type NodeNetworkErrorCode = 'ENOTFOUND' | 'ETIMEDOUT' | 'EAI_AGAIN' | 'ECONNRESET' | 'ECONNREFUSED' | 'ERRADDRINUSE' | 'EADDRNOTAVAIL' | 'ECONNABORTED' | 'EHOSTUNREACH'; export const NETWORK_ERROR_CODES: NodeNetworkErrorCode[] = ['ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN', 'ECONNRESET', 'ECONNREFUSED', 'ERRADDRINUSE', 'EADDRNOTAVAIL', 'ECONNABORTED', 'EHOSTUNREACH']; diff --git a/src/backend/common/errors/UpstreamError.ts b/src/backend/common/errors/UpstreamError.ts index 452385a3..fc0ab357 100644 --- a/src/backend/common/errors/UpstreamError.ts +++ b/src/backend/common/errors/UpstreamError.ts @@ -1,5 +1,5 @@ +import { Response } from 'superagent'; import { findCauseByFunc } from "../../utils.js"; -import {Response} from 'superagent'; export class UpstreamError extends Error { diff --git a/src/backend/common/infrastructure/Atomic.ts b/src/backend/common/infrastructure/Atomic.ts index 1b36c804..e27dfffe 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/logging'; +import { Logger } from '@foxxmd/logging'; +import { Dayjs } from "dayjs"; +import { Request, Response } from "express"; +import { NextFunction, ParamsDictionary, Query } from "express-serve-static-core"; +import { FixedSizeList } from 'fixed-size-list'; +import { PlayMeta, PlayObject } from "../../../core/Atomic.js"; import TupleMap from "../TupleMap.js"; -import {Request, Response} from "express"; -import {NextFunction, ParamsDictionary, Query} from "express-serve-static-core"; -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']; diff --git a/src/backend/common/infrastructure/config/aioConfig.ts b/src/backend/common/infrastructure/config/aioConfig.ts index 1eb73bd2..7546983a 100644 --- a/src/backend/common/infrastructure/config/aioConfig.ts +++ b/src/backend/common/infrastructure/config/aioConfig.ts @@ -1,9 +1,9 @@ -import { CommonSourceOptions, ScrobbleThresholds, SourceRetryOptions } from "./source/index.js"; -import { RequestRetryOptions } from "./common.js"; -import { SourceAIOConfig } from "./source/sources.js"; +import { LogOptions } from "@foxxmd/logging"; import { ClientAIOConfig } from "./client/clients.js"; +import { RequestRetryOptions } from "./common.js"; import { WebhookConfig } from "./health/webhooks.js"; -import {LogOptions} from "@foxxmd/logging"; +import { CommonSourceOptions, ScrobbleThresholds, SourceRetryOptions } from "./source/index.js"; +import { SourceAIOConfig } from "./source/sources.js"; export interface SourceDefaults extends SourceRetryOptions { diff --git a/src/backend/common/infrastructure/config/client/clients.ts b/src/backend/common/infrastructure/config/client/clients.ts index ed5cd584..535d1607 100644 --- a/src/backend/common/infrastructure/config/client/clients.ts +++ b/src/backend/common/infrastructure/config/client/clients.ts @@ -1,6 +1,6 @@ -import { MalojaClientAIOConfig, MalojaClientConfig } from "./maloja.js"; import { LastfmClientAIOConfig, LastfmClientConfig } from "./lastfm.js"; import { ListenBrainzClientAIOConfig, ListenBrainzClientConfig } from "./listenbrainz.js"; +import { MalojaClientAIOConfig, MalojaClientConfig } from "./maloja.js"; export type ClientConfig = MalojaClientConfig | LastfmClientConfig | ListenBrainzClientConfig; diff --git a/src/backend/common/infrastructure/config/client/lastfm.ts b/src/backend/common/infrastructure/config/client/lastfm.ts index 4558230a..2507d721 100644 --- a/src/backend/common/infrastructure/config/client/lastfm.ts +++ b/src/backend/common/infrastructure/config/client/lastfm.ts @@ -1,5 +1,5 @@ -import { CommonClientConfig, CommonClientData } from "./index.js"; import { RequestRetryOptions } from "../common.js"; +import { CommonClientConfig, CommonClientData } from "./index.js"; export interface LastfmData extends RequestRetryOptions { /** diff --git a/src/backend/common/infrastructure/config/client/listenbrainz.ts b/src/backend/common/infrastructure/config/client/listenbrainz.ts index ee8e0835..a852db39 100644 --- a/src/backend/common/infrastructure/config/client/listenbrainz.ts +++ b/src/backend/common/infrastructure/config/client/listenbrainz.ts @@ -1,5 +1,5 @@ -import { CommonClientConfig, CommonClientData } from "./index.js"; import { RequestRetryOptions } from "../common.js"; +import { CommonClientConfig, CommonClientData } from "./index.js"; export interface ListenBrainzData extends RequestRetryOptions{ /** diff --git a/src/backend/common/infrastructure/config/client/maloja.ts b/src/backend/common/infrastructure/config/client/maloja.ts index 633f4daf..59d9170e 100644 --- a/src/backend/common/infrastructure/config/client/maloja.ts +++ b/src/backend/common/infrastructure/config/client/maloja.ts @@ -1,5 +1,5 @@ -import { CommonClientConfig, CommonClientData } from "./index.js"; import { RequestRetryOptions } from "../common.js"; +import { CommonClientConfig, CommonClientData } from "./index.js"; export interface MalojaClientData extends RequestRetryOptions, CommonClientData { /** diff --git a/src/backend/common/infrastructure/config/source/jriver.ts b/src/backend/common/infrastructure/config/source/jriver.ts index 6459610e..e53c2a40 100644 --- a/src/backend/common/infrastructure/config/source/jriver.ts +++ b/src/backend/common/infrastructure/config/source/jriver.ts @@ -1,5 +1,5 @@ -import { CommonSourceConfig, CommonSourceData } from "./index.js"; import { PollingOptions } from "../common.js"; +import { CommonSourceConfig, CommonSourceData } from "./index.js"; export interface JRiverData extends CommonSourceData, PollingOptions { /** diff --git a/src/backend/common/infrastructure/config/source/kodi.ts b/src/backend/common/infrastructure/config/source/kodi.ts index b38dbcfd..f7680940 100644 --- a/src/backend/common/infrastructure/config/source/kodi.ts +++ b/src/backend/common/infrastructure/config/source/kodi.ts @@ -1,5 +1,5 @@ -import { CommonSourceConfig, CommonSourceData } from "./index.js"; import { PollingOptions } from "../common.js"; +import { CommonSourceConfig, CommonSourceData } from "./index.js"; export interface KodiData extends CommonSourceData, PollingOptions { diff --git a/src/backend/common/infrastructure/config/source/lastfm.ts b/src/backend/common/infrastructure/config/source/lastfm.ts index a3faafe1..4360c4aa 100644 --- a/src/backend/common/infrastructure/config/source/lastfm.ts +++ b/src/backend/common/infrastructure/config/source/lastfm.ts @@ -1,6 +1,6 @@ import { LastfmData } from "../client/lastfm.js"; -import { CommonSourceConfig, CommonSourceData } from "./index.js"; import { PollingOptions } from "../common.js"; +import { CommonSourceConfig, CommonSourceData } from "./index.js"; export interface LastFmSourceData extends CommonSourceData, PollingOptions, LastfmData{} diff --git a/src/backend/common/infrastructure/config/source/listenbrainz.ts b/src/backend/common/infrastructure/config/source/listenbrainz.ts index 06ed18e4..b6316fa3 100644 --- a/src/backend/common/infrastructure/config/source/listenbrainz.ts +++ b/src/backend/common/infrastructure/config/source/listenbrainz.ts @@ -1,6 +1,6 @@ -import { CommonSourceConfig, CommonSourceData } from "./index.js"; import { ListenBrainzData } from "../client/listenbrainz.js"; import { PollingOptions } from "../common.js"; +import { CommonSourceConfig, CommonSourceData } from "./index.js"; export interface ListenBrainzSourceData extends ListenBrainzData, CommonSourceData, PollingOptions { } diff --git a/src/backend/common/infrastructure/config/source/mopidy.ts b/src/backend/common/infrastructure/config/source/mopidy.ts index bd0ab082..bf4b1d12 100644 --- a/src/backend/common/infrastructure/config/source/mopidy.ts +++ b/src/backend/common/infrastructure/config/source/mopidy.ts @@ -1,5 +1,5 @@ -import { CommonSourceConfig, CommonSourceData } from "./index.js"; import { PollingOptions } from "../common.js"; +import { CommonSourceConfig, CommonSourceData } from "./index.js"; export interface MopidyData extends CommonSourceData, PollingOptions { /** diff --git a/src/backend/common/infrastructure/config/source/sources.ts b/src/backend/common/infrastructure/config/source/sources.ts index 9798e2a2..dc76a456 100644 --- a/src/backend/common/infrastructure/config/source/sources.ts +++ b/src/backend/common/infrastructure/config/source/sources.ts @@ -1,18 +1,18 @@ -import { SpotifySourceAIOConfig, SpotifySourceConfig } from "./spotify.js"; -import { PlexSourceAIOConfig, PlexSourceConfig } from "./plex.js"; -import { TautulliSourceAIOConfig, TautulliSourceConfig } from "./tautulli.js"; +import { ChromecastSourceAIOConfig, ChromecastSourceConfig } from "./chromecast.js"; import { DeezerSourceAIOConfig, DeezerSourceConfig } from "./deezer.js"; -import { SubsonicSourceAIOConfig, SubSonicSourceConfig } from "./subsonic.js"; import { JellySourceAIOConfig, JellySourceConfig } from "./jellyfin.js"; -import { LastFmSouceAIOConfig, LastfmSourceConfig } from "./lastfm.js"; -import { YTMusicSourceAIOConfig, YTMusicSourceConfig } from "./ytmusic.js"; -import { MPRISSourceAIOConfig, MPRISSourceConfig } from "./mpris.js"; -import { MopidySourceAIOConfig, MopidySourceConfig } from "./mopidy.js"; -import { ListenBrainzSourceAIOConfig, ListenBrainzSourceConfig } from "./listenbrainz.js"; import { JRiverSourceAIOConfig, JRiverSourceConfig } from "./jriver.js"; import { KodiSourceAIOConfig, KodiSourceConfig } from "./kodi.js"; +import { LastFmSouceAIOConfig, LastfmSourceConfig } from "./lastfm.js"; +import { ListenBrainzSourceAIOConfig, ListenBrainzSourceConfig } from "./listenbrainz.js"; +import { MopidySourceAIOConfig, MopidySourceConfig } from "./mopidy.js"; +import { MPRISSourceAIOConfig, MPRISSourceConfig } from "./mpris.js"; +import { PlexSourceAIOConfig, PlexSourceConfig } from "./plex.js"; +import { SpotifySourceAIOConfig, SpotifySourceConfig } from "./spotify.js"; +import { SubsonicSourceAIOConfig, SubSonicSourceConfig } from "./subsonic.js"; +import { TautulliSourceAIOConfig, TautulliSourceConfig } from "./tautulli.js"; import { WebScrobblerSourceAIOConfig, WebScrobblerSourceConfig } from "./webscrobbler.js"; -import { ChromecastSourceConfig, ChromecastSourceAIOConfig } from "./chromecast.js"; +import { YTMusicSourceAIOConfig, YTMusicSourceConfig } from "./ytmusic.js"; export type SourceConfig = SpotifySourceConfig | PlexSourceConfig | TautulliSourceConfig | DeezerSourceConfig | SubSonicSourceConfig | JellySourceConfig | LastfmSourceConfig | YTMusicSourceConfig | MPRISSourceConfig | MopidySourceConfig | ListenBrainzSourceConfig | JRiverSourceConfig | KodiSourceConfig | WebScrobblerSourceConfig | ChromecastSourceConfig; diff --git a/src/backend/common/infrastructure/config/source/spotify.ts b/src/backend/common/infrastructure/config/source/spotify.ts index ed624638..742e0daf 100644 --- a/src/backend/common/infrastructure/config/source/spotify.ts +++ b/src/backend/common/infrastructure/config/source/spotify.ts @@ -1,5 +1,5 @@ -import { CommonSourceConfig, CommonSourceData } from "./index.js"; import { PollingOptions } from "../common.js"; +import { CommonSourceConfig, CommonSourceData } from "./index.js"; export interface SpotifySourceData extends CommonSourceData, PollingOptions { /** diff --git a/src/backend/common/infrastructure/config/source/subsonic.ts b/src/backend/common/infrastructure/config/source/subsonic.ts index 6a34c959..63a5999d 100644 --- a/src/backend/common/infrastructure/config/source/subsonic.ts +++ b/src/backend/common/infrastructure/config/source/subsonic.ts @@ -1,5 +1,5 @@ -import { CommonSourceConfig, CommonSourceData } from "./index.js"; import { PollingOptions } from "../common.js"; +import { CommonSourceConfig, CommonSourceData } from "./index.js"; export interface SubsonicData extends CommonSourceData, PollingOptions { /** diff --git a/src/backend/common/infrastructure/config/source/ytmusic.ts b/src/backend/common/infrastructure/config/source/ytmusic.ts index 4e858c4b..a5b71e00 100644 --- a/src/backend/common/infrastructure/config/source/ytmusic.ts +++ b/src/backend/common/infrastructure/config/source/ytmusic.ts @@ -1,5 +1,5 @@ -import { CommonSourceConfig, CommonSourceData } from "./index.js"; import { PollingOptions } from "../common.js"; +import { CommonSourceConfig, CommonSourceData } from "./index.js"; export interface YTMusicData extends CommonSourceData, PollingOptions { /** diff --git a/src/backend/common/infrastructure/typings/passport-deezer.d.ts b/src/backend/common/infrastructure/typings/passport-deezer.d.ts index 21c498cd..22015895 100644 --- a/src/backend/common/infrastructure/typings/passport-deezer.d.ts +++ b/src/backend/common/infrastructure/typings/passport-deezer.d.ts @@ -1,7 +1,8 @@ declare module 'passport-deezer' { //import {Strategy as Oauth2Strategy} from 'passport-oauth2'; - import {Strategy as PassportStrategy} from "passport"; + import { Strategy as PassportStrategy } from "passport"; + export class Strategy extends PassportStrategy { constructor(options: any, verify: any); } diff --git a/src/backend/common/logging.ts b/src/backend/common/logging.ts index 6b58639d..45ac8379 100644 --- a/src/backend/common/logging.ts +++ b/src/backend/common/logging.ts @@ -1,16 +1,16 @@ -import path from "path"; -import { projectDir } from "./index.js"; -import process from "process"; import { - parseLogOptions, + childLogger, + Logger as FoxLogger, loggerAppRolling, + LogLevel, LogOptions as FoxLogOptions, - Logger as FoxLogger, - childLogger, LogLevel, PrettyOptionsExtra, + parseLogOptions, } from '@foxxmd/logging'; -import {PassThrough, Transform} from "node:stream"; -import {buildLogger, buildDestinationStdout, buildDestinationJsonPrettyStream} from "@foxxmd/logging/factory"; -import {parseBool} from "../utils.js"; +import { buildDestinationJsonPrettyStream, buildDestinationStdout, buildLogger } from "@foxxmd/logging/factory"; +import { PassThrough, Transform } from "node:stream"; +import path from "path"; +import process from "process"; +import { projectDir } from "./index.js"; export let logPath = path.resolve(projectDir, `./logs`); if (typeof process.env.CONFIG_DIR === 'string') { diff --git a/src/backend/common/vendor/AbstractApiClient.ts b/src/backend/common/vendor/AbstractApiClient.ts index 8357e7d1..99cf8391 100644 --- a/src/backend/common/vendor/AbstractApiClient.ts +++ b/src/backend/common/vendor/AbstractApiClient.ts @@ -1,7 +1,7 @@ -import {childLogger, Logger} from "@foxxmd/logging"; -import {AbstractApiOptions, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; +import { childLogger, Logger } from "@foxxmd/logging"; import { PlayObject } from "../../../core/Atomic.js"; import { capitalize } from "../../../core/StringUtils.js"; +import { AbstractApiOptions, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; export default abstract class AbstractApiClient { name: string; diff --git a/src/backend/common/vendor/JRiverApiClient.ts b/src/backend/common/vendor/JRiverApiClient.ts index a9d40f9a..c2b2c6c7 100644 --- a/src/backend/common/vendor/JRiverApiClient.ts +++ b/src/backend/common/vendor/JRiverApiClient.ts @@ -1,8 +1,8 @@ -import AbstractApiClient from "./AbstractApiClient.js"; -import {JRiverData} from "../infrastructure/config/source/jriver.js"; -import request, {Request, Response} from 'superagent'; +import request, { Request, Response } from 'superagent'; import xml2js from 'xml2js'; -import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER} from "../infrastructure/Atomic.js"; +import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER } from "../infrastructure/Atomic.js"; +import { JRiverData } from "../infrastructure/config/source/jriver.js"; +import AbstractApiClient from "./AbstractApiClient.js"; const parser = new xml2js.Parser({'async': true}); diff --git a/src/backend/common/vendor/KodiApiClient.ts b/src/backend/common/vendor/KodiApiClient.ts index 61b63eb8..b6e2390a 100644 --- a/src/backend/common/vendor/KodiApiClient.ts +++ b/src/backend/common/vendor/KodiApiClient.ts @@ -1,12 +1,12 @@ -import AbstractApiClient from "./AbstractApiClient.js"; -import { KodiData } from "../infrastructure/config/source/kodi.js"; +import dayjs from "dayjs"; import { KodiClient } from 'kodi-api' import normalizeUrl from "normalize-url"; -import {URL} from "url"; -import { RecentlyPlayedOptions } from "../../sources/AbstractSource.js"; -import {AbstractApiOptions, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; -import dayjs from "dayjs"; +import { URL } from "url"; import { PlayObject } from "../../../core/Atomic.js"; +import { RecentlyPlayedOptions } from "../../sources/AbstractSource.js"; +import { AbstractApiOptions, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; +import { KodiData } from "../infrastructure/config/source/kodi.js"; +import AbstractApiClient from "./AbstractApiClient.js"; interface KodiDuration { hours: number diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index 4f0d6595..bc2c4136 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -1,3 +1,4 @@ +import dayjs from "dayjs"; import LastFm, { AuthGetSessionResponse, NowPlayingResponse, @@ -5,16 +6,15 @@ import LastFm, { TrackScrobblePayload, UserGetInfoResponse } from "lastfm-node-client"; -import AbstractApiClient from "./AbstractApiClient.js"; -import dayjs from "dayjs"; -import {readJson, removeUndefinedKeys, sleep, writeFile} from "../../utils.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"; -import {nonEmptyStringOrDefault, splitByFirstFound} from "../../../core/StringUtils.js"; -import {getScrobbleTsSOCDate} from "../../utils/TimeUtils.js"; -import {UpstreamError} from "../errors/UpstreamError.js"; +import { nonEmptyStringOrDefault, splitByFirstFound } from "../../../core/StringUtils.js"; +import { readJson, removeUndefinedKeys, sleep, writeFile } from "../../utils.js"; +import { getScrobbleTsSOCDate } from "../../utils/TimeUtils.js"; +import { getNodeNetworkException, isNodeNetworkException } from "../errors/NodeErrors.js"; +import { UpstreamError } from "../errors/UpstreamError.js"; +import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; +import { LastfmData } from "../infrastructure/config/client/lastfm.js"; +import AbstractApiClient from "./AbstractApiClient.js"; const badErrors = [ 'api key suspended', diff --git a/src/backend/common/vendor/ListenbrainzApiClient.ts b/src/backend/common/vendor/ListenbrainzApiClient.ts index 417836d2..a4bc2226 100644 --- a/src/backend/common/vendor/ListenbrainzApiClient.ts +++ b/src/backend/common/vendor/ListenbrainzApiClient.ts @@ -1,12 +1,9 @@ -import AbstractApiClient from "./AbstractApiClient.js"; -import request, {Request} from 'superagent'; -import { ListenBrainzClientData } from "../infrastructure/config/client/listenbrainz.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"; +import dayjs from "dayjs"; +import request, { Request } from 'superagent'; import { PlayObject } from "../../../core/Atomic.js"; import { slice } from "../../../core/StringUtils.js"; +import { combinePartsToString } from "../../utils.js"; import { findDelimiters, normalizeStr, @@ -15,8 +12,11 @@ import { parseTrackCredits, uniqueNormalizedStrArr, } from "../../utils/StringUtils.js"; -import { UpstreamError } from "../errors/UpstreamError.js"; import { getScrobbleTsSOCDate } from "../../utils/TimeUtils.js"; +import { UpstreamError } from "../errors/UpstreamError.js"; +import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; +import { ListenBrainzClientData } from "../infrastructure/config/client/listenbrainz.js"; +import AbstractApiClient from "./AbstractApiClient.js"; export interface ArtistMBIDMapping { diff --git a/src/backend/common/vendor/chromecast/ChromecastClientUtils.ts b/src/backend/common/vendor/chromecast/ChromecastClientUtils.ts index cfcd11a3..ad59291d 100644 --- a/src/backend/common/vendor/chromecast/ChromecastClientUtils.ts +++ b/src/backend/common/vendor/chromecast/ChromecastClientUtils.ts @@ -1,8 +1,8 @@ -import { REPORTED_PLAYER_STATUSES, ReportedPlayerStatus } from "../../infrastructure/Atomic.js"; -import { PlatformApplication, PlatformType } from "./interfaces.js"; -import {Media, MediaController, Result} from "@foxxmd/chromecast-client"; +import { Media, MediaController, Result } from "@foxxmd/chromecast-client"; import objectHash from "object-hash"; import { PlayObject } from "../../../../core/Atomic.js"; +import { REPORTED_PLAYER_STATUSES, ReportedPlayerStatus } from "../../infrastructure/Atomic.js"; +import { PlatformApplication, PlatformType } from "./interfaces.js"; export const chromePlayerStateToReported = (state: string): ReportedPlayerStatus => { switch (state) { diff --git a/src/backend/common/vendor/chromecast/interfaces.ts b/src/backend/common/vendor/chromecast/interfaces.ts index 41494d1c..f2929989 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 { createPlatform, MediaController } from "@foxxmd/chromecast-client"; +import { Logger } from "@foxxmd/logging"; +import { Dayjs } from "dayjs"; import { FormatPlayObjectOptions } from "../../infrastructure/Atomic.js"; -import {Dayjs} from "dayjs"; -import {Logger} from "@foxxmd/logging"; export type PlatformType = ReturnType; export interface PlatformApplication { diff --git a/src/backend/common/vendor/maloja/interfaces.ts b/src/backend/common/vendor/maloja/interfaces.ts index f53e4e1f..a3ac3724 100644 --- a/src/backend/common/vendor/maloja/interfaces.ts +++ b/src/backend/common/vendor/maloja/interfaces.ts @@ -1,4 +1,4 @@ -import {ResponseError} from "superagent"; +import { ResponseError } from "superagent"; import { findCauseByFunc } from "../../../utils.js"; import { isSuperAgentResponseError } from "../../errors/ErrorUtils.js"; diff --git a/src/backend/common/vendor/subsonic/interfaces.ts b/src/backend/common/vendor/subsonic/interfaces.ts index 54f1b5ef..a3e384a3 100644 --- a/src/backend/common/vendor/subsonic/interfaces.ts +++ b/src/backend/common/vendor/subsonic/interfaces.ts @@ -1,4 +1,4 @@ -import {Response} from 'superagent'; +import { Response } from 'superagent'; export interface SubsonicResponseCommon { status: 'failed' | 'ok' diff --git a/src/backend/index.ts b/src/backend/index.ts index 340bc2e4..524efa23 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -1,23 +1,21 @@ import 'dotenv/config'; -import {LogDataPretty, Logger} from "@foxxmd/logging"; +import { childLogger, LogDataPretty, Logger as FoxLogger } from "@foxxmd/logging"; import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc.js'; +import duration from 'dayjs/plugin/duration.js'; import isBetween from 'dayjs/plugin/isBetween.js'; import relativeTime from 'dayjs/plugin/relativeTime.js'; -import duration from 'dayjs/plugin/duration.js'; import timezone from 'dayjs/plugin/timezone.js'; -import { parseBool, readJson, sleep } from "./utils.js"; +import utc from 'dayjs/plugin/utc.js'; import * as path from "path"; +import { SimpleIntervalJob, ToadScheduler } from "toad-scheduler"; import { projectDir } from "./common/index.js"; -import SpotifySource from "./sources/SpotifySource.js"; import { AIOConfig } from "./common/infrastructure/config/aioConfig.js"; +import { appLogger, initLogger as getInitLogger } from "./common/logging.js"; import { getRoot } from "./ioc.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 {loggerDebug, childLogger, LogData, Logger as FoxLogger} from '@foxxmd/logging'; +import { createHeartbeatSourcesTask } from "./tasks/heartbeatSources.js"; +import { parseBool, readJson, sleep } from "./utils.js"; dayjs.extend(utc) dayjs.extend(isBetween); diff --git a/src/backend/ioc.ts b/src/backend/ioc.ts index 31312f98..cfa7899d 100644 --- a/src/backend/ioc.ts +++ b/src/backend/ioc.ts @@ -1,15 +1,15 @@ -import {createContainer} from "iti"; +import { Logger } from "@foxxmd/logging"; +import { EventEmitter } from "events"; +import fs from 'fs'; +import { createContainer } from "iti"; +import normalizeUrl from 'normalize-url'; import path from "path"; -import { configDir, projectDir } from "./common/index.js"; -import ScrobbleClients from "./scrobblers/ScrobbleClients.js"; -import ScrobbleSources from "./sources/ScrobbleSources.js"; -import { Notifiers } from "./notifier/Notifiers.js"; -import {EventEmitter} from "events"; +import { projectDir } from "./common/index.js"; 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"; +import { Notifiers } from "./notifier/Notifiers.js"; +import ScrobbleClients from "./scrobblers/ScrobbleClients.js"; +import ScrobbleSources from "./sources/ScrobbleSources.js"; let version = 'Unknown'; diff --git a/src/backend/notifier/AbstractWebhookNotifier.ts b/src/backend/notifier/AbstractWebhookNotifier.ts index bba6be35..4181a09b 100644 --- a/src/backend/notifier/AbstractWebhookNotifier.ts +++ b/src/backend/notifier/AbstractWebhookNotifier.ts @@ -1,6 +1,5 @@ +import { childLogger, Logger } from "@foxxmd/logging"; import { GotifyConfig, NtfyConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; -import {childLogger, Logger} from "@foxxmd/logging"; -import { mergeArr } from "../utils.js"; export abstract class AbstractWebhookNotifier { diff --git a/src/backend/notifier/GotifyWebhookNotifier.ts b/src/backend/notifier/GotifyWebhookNotifier.ts index f6a4af28..fa6f93b6 100644 --- a/src/backend/notifier/GotifyWebhookNotifier.ts +++ b/src/backend/notifier/GotifyWebhookNotifier.ts @@ -1,9 +1,9 @@ -import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; -import { GotifyConfig, PrioritiesConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; -import {gotify} from 'gotify'; +import { Logger } from "@foxxmd/logging"; +import { HTTPError } from "got"; +import { gotify } from 'gotify'; import request from 'superagent'; -import {HTTPError} from "got"; -import {Logger} from "@foxxmd/logging"; +import { GotifyConfig, PrioritiesConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; +import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; export class GotifyWebhookNotifier extends AbstractWebhookNotifier { diff --git a/src/backend/notifier/Notifiers.ts b/src/backend/notifier/Notifiers.ts index 5ddb6cca..8fae13b2 100644 --- a/src/backend/notifier/Notifiers.ts +++ b/src/backend/notifier/Notifiers.ts @@ -1,9 +1,14 @@ -import {childLogger, Logger} from '@foxxmd/logging'; -import { GotifyConfig, NtfyConfig, WebhookConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; +import { childLogger, Logger } from '@foxxmd/logging'; +import { EventEmitter } from "events"; +import { + GotifyConfig, + NtfyConfig, + WebhookConfig, + WebhookPayload +} from "../common/infrastructure/config/health/webhooks.js"; import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; import { GotifyWebhookNotifier } from "./GotifyWebhookNotifier.js"; import { NtfyWebhookNotifier } from "./NtfyWebhookNotifier.js"; -import {EventEmitter} from "events"; export class Notifiers { diff --git a/src/backend/notifier/NtfyWebhookNotifier.ts b/src/backend/notifier/NtfyWebhookNotifier.ts index 000cf741..a5aeeae7 100644 --- a/src/backend/notifier/NtfyWebhookNotifier.ts +++ b/src/backend/notifier/NtfyWebhookNotifier.ts @@ -1,9 +1,9 @@ -import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; -import { NtfyConfig, PrioritiesConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; -import {publish} from 'ntfy'; +import { Logger } from "@foxxmd/logging"; +import { publish } from 'ntfy'; +import { Config } from "ntfy/interfaces.js"; import request from "superagent"; -import {Logger} from "@foxxmd/logging"; -import {Config} from "ntfy/interfaces.js"; +import { NtfyConfig, PrioritiesConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; +import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; export class NtfyWebhookNotifier extends AbstractWebhookNotifier { diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index fc78a737..d9d7f4df 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -1,13 +1,20 @@ -import dayjs, {Dayjs} from "dayjs"; +import { childLogger, Logger } from "@foxxmd/logging"; +import dayjs, { Dayjs } from "dayjs"; +import EventEmitter from "events"; +import { FixedSizeList } from 'fixed-size-list'; +import { nanoid } from "nanoid"; +import { messageWithCauses } from "pony-cause"; import { - comparingMultipleArtists, - mergeArr, - playObjDataMatch, - pollingBackoff, - setIntersection, - sleep, - sortByOldestPlayDate, -} from "../utils.js"; + DeadLetterScrobble, + PlayObject, + QueuedScrobble, + TA_CLOSE, + TA_FUZZY, + TrackStringOptions, +} from "../../core/Atomic.js"; +import { buildTrackString, capitalize, truncateStringToLength } from "../../core/StringUtils.js"; +import { hasNodeNetworkException } from "../common/errors/NodeErrors.js"; +import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; import { ARTIST_WEIGHT, Authenticatable, @@ -19,30 +26,21 @@ import { INITIALIZING, InitState, NOT_INITIALIZED, - REFERENCE_WEIGHT, ScrobbledPlayObject, TIME_WEIGHT, TITLE_WEIGHT, } from "../common/infrastructure/Atomic.js"; -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'; import { - DeadLetterScrobble, - PlayObject, - QueuedScrobble, - TA_CLOSE, - TA_FUZZY, - TrackStringOptions, -} from "../../core/Atomic.js"; -import { buildTrackString, capitalize, truncateStringToLength } from "../../core/StringUtils.js"; -import EventEmitter from "events"; + comparingMultipleArtists, + playObjDataMatch, + pollingBackoff, + setIntersection, + sleep, + sortByOldestPlayDate, +} from "../utils.js"; import { compareScrobbleArtists, compareScrobbleTracks, normalizeStr } from "../utils/StringUtils.js"; -import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; -import {nanoid} from "nanoid"; -import {messageWithCauses} from "pony-cause"; -import { hasNodeNetworkException } from "../common/errors/NodeErrors.js"; import { comparePlayTemporally, temporalAccuracyIsAtLeast, diff --git a/src/backend/scrobblers/LastfmScrobbler.ts b/src/backend/scrobblers/LastfmScrobbler.ts index 7f776f0c..c42c1b6d 100644 --- a/src/backend/scrobblers/LastfmScrobbler.ts +++ b/src/backend/scrobblers/LastfmScrobbler.ts @@ -1,25 +1,16 @@ -import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; +import { Logger } from "@foxxmd/logging"; import dayjs from 'dayjs'; - -import { - playObjDataMatch, - removeUndefinedKeys, - setIntersection, - sleep, - sortByOldestPlayDate, -} from "../utils.js"; -import LastfmApiClient from "../common/vendor/LastfmApiClient.js"; -import { FormatPlayObjectOptions, INITIALIZING, ScrobbledPlayObject } from "../common/infrastructure/Atomic.js"; -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/logging"; -import { PlayObject, TrackStringOptions } from "../../core/Atomic.js"; -import { buildTrackString, capitalize } from "../../core/StringUtils.js"; import EventEmitter from "events"; -import { UpstreamError } from "../common/errors/UpstreamError.js"; +import { TrackScrobbleResponse, UserGetRecentTracksResponse } from "lastfm-node-client"; +import { PlayObject } from "../../core/Atomic.js"; +import { buildTrackString, capitalize } from "../../core/StringUtils.js"; import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import { getScrobbleTsSOCDate } from "../utils/TimeUtils.js"; +import { UpstreamError } from "../common/errors/UpstreamError.js"; +import { FormatPlayObjectOptions, INITIALIZING } from "../common/infrastructure/Atomic.js"; +import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; +import LastfmApiClient from "../common/vendor/LastfmApiClient.js"; +import { Notifiers } from "../notifier/Notifiers.js"; +import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; export default class LastfmScrobbler extends AbstractScrobbleClient { diff --git a/src/backend/scrobblers/ListenbrainzScrobbler.ts b/src/backend/scrobblers/ListenbrainzScrobbler.ts index 81c7c0a7..473d8eae 100644 --- a/src/backend/scrobblers/ListenbrainzScrobbler.ts +++ b/src/backend/scrobblers/ListenbrainzScrobbler.ts @@ -1,16 +1,16 @@ +import { Logger } from "@foxxmd/logging"; import dayjs from 'dayjs'; - -import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; +import EventEmitter from "events"; +import { PlayObject } from "../../core/Atomic.js"; +import { buildTrackString, capitalize } from "../../core/StringUtils.js"; +import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; +import { UpstreamError } from "../common/errors/UpstreamError.js"; import { FormatPlayObjectOptions, INITIALIZING } from "../common/infrastructure/Atomic.js"; -import { Notifiers } from "../notifier/Notifiers.js"; -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"; -import { buildTrackString, capitalize } from "../../core/StringUtils.js"; -import EventEmitter from "events"; -import { UpstreamError } from "../common/errors/UpstreamError.js"; -import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; +import { Notifiers } from "../notifier/Notifiers.js"; + +import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; export default class ListenbrainzScrobbler extends AbstractScrobbleClient { diff --git a/src/backend/scrobblers/MalojaScrobbler.ts b/src/backend/scrobblers/MalojaScrobbler.ts index 9d498a96..e1d1c6ea 100644 --- a/src/backend/scrobblers/MalojaScrobbler.ts +++ b/src/backend/scrobblers/MalojaScrobbler.ts @@ -1,12 +1,16 @@ -import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; -import request, {ResponseError, SuperAgentRequest} from 'superagent'; -import dayjs from 'dayjs'; +import { Logger } from "@foxxmd/logging"; import compareVersions from 'compare-versions'; -import { sleep, parseRetryAfterSecsFromObj } from "../utils.js"; +import dayjs from 'dayjs'; +import EventEmitter from "events"; +import normalizeUrl from "normalize-url"; +import request, { SuperAgentRequest } from 'superagent'; +import { PlayObject } from "../../core/Atomic.js"; +import { buildTrackString, capitalize } from "../../core/StringUtils.js"; +import { isSuperAgentResponseError } from "../common/errors/ErrorUtils.js"; +import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; +import { UpstreamError } from "../common/errors/UpstreamError.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/logging"; import { getMalojaResponseError, isMalojaAPIErrorBody, @@ -19,14 +23,10 @@ import { MalojaV2ScrobbleData, MalojaV3ScrobbleData, } from "../common/vendor/maloja/interfaces.js"; -import { PlayObject, TrackStringOptions } from "../../core/Atomic.js"; -import { buildTrackString, capitalize } from "../../core/StringUtils.js"; -import EventEmitter from "events"; -import normalizeUrl from "normalize-url"; -import { UpstreamError } from "../common/errors/UpstreamError.js"; +import { Notifiers } from "../notifier/Notifiers.js"; +import { parseRetryAfterSecsFromObj, sleep } from "../utils.js"; import { getScrobbleTsSOCDate, getScrobbleTsSOCDateWithContext } from "../utils/TimeUtils.js"; -import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import { isSuperAgentResponseError } from "../common/errors/ErrorUtils.js"; +import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; const feat = ["ft.", "ft", "feat.", "feat", "featuring", "Ft.", "Ft", "Feat.", "Feat", "Featuring"]; diff --git a/src/backend/scrobblers/ScrobbleClients.ts b/src/backend/scrobblers/ScrobbleClients.ts index 42280b2c..60c014d1 100644 --- a/src/backend/scrobblers/ScrobbleClients.ts +++ b/src/backend/scrobblers/ScrobbleClients.ts @@ -1,25 +1,22 @@ /* eslint-disable no-case-declarations */ -import dayjs, {Dayjs} from "dayjs"; -import { - readJson, - validateJson, -} from "../utils.js"; -import MalojaScrobbler from "./MalojaScrobbler.js"; -import LastfmScrobbler from "./LastfmScrobbler.js"; +import { childLogger, Logger } from '@foxxmd/logging'; +import dayjs, { Dayjs } from "dayjs"; +import { PlayObject } from "../../core/Atomic.js"; import { clientTypes, ConfigMeta } from "../common/infrastructure/Atomic.js"; import { AIOConfig } from "../common/infrastructure/config/aioConfig.js"; -import * as aioSchema from '../common/schema/aio-client.json'; -import * as clientSchema from '../common/schema/client.json'; import { ClientAIOConfig, ClientConfig } from "../common/infrastructure/config/client/clients.js"; -import { MalojaClientConfig } from "../common/infrastructure/config/client/maloja.js"; import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; +import { ListenBrainzClientConfig } from "../common/infrastructure/config/client/listenbrainz.js"; +import { MalojaClientConfig } from "../common/infrastructure/config/client/maloja.js"; +import * as aioSchema from '../common/schema/aio-client.json'; +import * as clientSchema from '../common/schema/client.json'; +import { WildcardEmitter } from "../common/WildcardEmitter.js"; import { Notifiers } from "../notifier/Notifiers.js"; +import { readJson, validateJson, } from "../utils.js"; import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; -import {childLogger, Logger} from '@foxxmd/logging'; +import LastfmScrobbler from "./LastfmScrobbler.js"; import ListenbrainzScrobbler from "./ListenbrainzScrobbler.js"; -import { ListenBrainzClientConfig } from "../common/infrastructure/config/client/listenbrainz.js"; -import { PlayObject } from "../../core/Atomic.js"; -import { WildcardEmitter } from "../common/WildcardEmitter.js"; +import MalojaScrobbler from "./MalojaScrobbler.js"; type groupedNamedConfigs = {[key: string]: ParsedConfig[]}; diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index 97fda5c2..d7ca2e38 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -1,32 +1,33 @@ -import {ExpressWithAsync} from "@awaitjs/express"; -import { getRoot } from "../ioc.js"; -import { makeClientCheckMiddle, makeSourceCheckMiddle } from "./middleware.js"; -import AbstractSource from "../sources/AbstractSource.js"; +import { ExpressWithAsync } from "@awaitjs/express"; +import { LogDataPretty, Logger, LogLevel } from "@foxxmd/logging"; +import bsseDef from 'better-sse'; +import bodyParser from "body-parser"; +import { FixedSizeList } from 'fixed-size-list'; +import { PassThrough } from "node:stream"; +import { Transform } from "stream"; import { ClientStatusData, - DeadLetterScrobble, LeveledLogData, + DeadLetterScrobble, + LeveledLogData, LogOutputConfig, - PlayObject, SOURCE_SOT, + PlayObject, + SOURCE_SOT, SourceStatusData, } from "../../core/Atomic.js"; -import {LogDataPretty, Logger, LogLevel} from "@foxxmd/logging"; -import {Transform} from "stream"; -import * as bsse from 'better-sse'; -import bsseDef from 'better-sse'; -import { setupTautulliRoutes } from "./tautulliRoutes.js"; -import { setupPlexRoutes } from "./plexRoutes.js"; -import { setupJellyfinRoutes } from "./jellyfinRoutes.js"; -import { setupDeezerRoutes } from "./deezerRoutes.js"; -import { setupAuthRoutes } from "./auth.js"; -import {ExpressHandler} from "../common/infrastructure/Atomic.js"; -import MemorySource from "../sources/MemorySource.js"; import { capitalize } from "../../core/StringUtils.js"; +import { ExpressHandler } from "../common/infrastructure/Atomic.js"; +import { getRoot } from "../ioc.js"; import AbstractScrobbleClient from "../scrobblers/AbstractScrobbleClient.js"; +import AbstractSource from "../sources/AbstractSource.js"; +import MemorySource from "../sources/MemorySource.js"; import { sortByNewestPlayDate } from "../utils.js"; -import bodyParser from "body-parser"; +import { setupAuthRoutes } from "./auth.js"; +import { setupDeezerRoutes } from "./deezerRoutes.js"; +import { setupJellyfinRoutes } from "./jellyfinRoutes.js"; +import { makeClientCheckMiddle, makeSourceCheckMiddle } from "./middleware.js"; +import { setupPlexRoutes } from "./plexRoutes.js"; +import { setupTautulliRoutes } from "./tautulliRoutes.js"; import { setupWebscrobblerRoutes } from "./webscrobblerRoutes.js"; -import {FixedSizeList} from 'fixed-size-list'; -import {PassThrough} from "node:stream"; const maxBufferSize = 300; const output: Record> = {}; diff --git a/src/backend/server/auth.ts b/src/backend/server/auth.ts index ecca9f87..10898a1d 100644 --- a/src/backend/server/auth.ts +++ b/src/backend/server/auth.ts @@ -1,11 +1,11 @@ -import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/logging"; -import ScrobbleSources from "../sources/ScrobbleSources.js"; +import { ExpressWithAsync } from "@awaitjs/express"; +import { Logger } from "@foxxmd/logging"; import passport from "passport"; import { ExpressHandler } from "../common/infrastructure/Atomic.js"; -import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; import LastfmScrobbler from "../scrobblers/LastfmScrobbler.js"; +import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; import LastfmSource from "../sources/LastfmSource.js"; +import ScrobbleSources from "../sources/ScrobbleSources.js"; import SpotifySource from "../sources/SpotifySource.js"; export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMiddle: ExpressHandler, clientMiddle: ExpressHandler, scrobbleSources: ScrobbleSources, scrobbleClients: ScrobbleClients) => { diff --git a/src/backend/server/deezerRoutes.ts b/src/backend/server/deezerRoutes.ts index 5c960363..bbe3b633 100644 --- a/src/backend/server/deezerRoutes.ts +++ b/src/backend/server/deezerRoutes.ts @@ -1,12 +1,9 @@ -import { ExpressHandler } from "../common/infrastructure/Atomic.js"; -import { mergeArr, parseBool, sleep } from "../utils.js"; -import {ExpressWithAsync} from "@awaitjs/express"; -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"; -import DeezerSource from "../sources/DeezerSource.js"; +import { ExpressWithAsync } from "@awaitjs/express"; +import { Logger } from "@foxxmd/logging"; import passport from "passport"; +import DeezerSource from "../sources/DeezerSource.js"; +import ScrobbleSources from "../sources/ScrobbleSources.js"; +import { sleep } from "../utils.js"; export const setupDeezerRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { diff --git a/src/backend/server/index.ts b/src/backend/server/index.ts index 4c60232d..56dcdb93 100644 --- a/src/backend/server/index.ts +++ b/src/backend/server/index.ts @@ -1,16 +1,15 @@ -import {addAsync, Router} from '@awaitjs/express'; -import express from 'express'; -import ViteExpress from "vite-express"; +import { addAsync, Router } from '@awaitjs/express'; +import { childLogger, LogDataPretty, Logger } from "@foxxmd/logging"; import bodyParser from 'body-parser'; -import passport from 'passport'; +import { stripIndents } from "common-tags"; +import express from 'express'; import session from 'express-session'; +import { PassThrough } from "node:stream"; +import passport from 'passport'; +import ViteExpress from "vite-express"; import { getRoot } from "../ioc.js"; +import { getAddress, parseBool } from "../utils.js"; import { setupApi } from "./api.js"; -import { getAddress, mergeArr, parseBool } from "../utils.js"; -import {stripIndents} from "common-tags"; -import {childLogger, LogData, LogDataPretty} from "@foxxmd/logging"; -import {PassThrough} from "node:stream"; -import {Logger} from '@foxxmd/logging'; const app = addAsync(express()); const router = Router(); diff --git a/src/backend/server/jellyfinRoutes.ts b/src/backend/server/jellyfinRoutes.ts index b356f950..08ce2f92 100644 --- a/src/backend/server/jellyfinRoutes.ts +++ b/src/backend/server/jellyfinRoutes.ts @@ -1,10 +1,10 @@ -import { parseBool, remoteHostIdentifiers } from "../utils.js"; -import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/logging"; -import ScrobbleSources from "../sources/ScrobbleSources.js"; +import { ExpressWithAsync } from "@awaitjs/express"; +import { Logger } from "@foxxmd/logging"; import bodyParser from "body-parser"; import { JellyfinNotifier } from "../sources/ingressNotifiers/JellyfinNotifier.js"; import JellyfinSource from "../sources/JellyfinSource.js"; +import ScrobbleSources from "../sources/ScrobbleSources.js"; +import { parseBool, remoteHostIdentifiers } from "../utils.js"; export const setupJellyfinRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { diff --git a/src/backend/server/middleware.ts b/src/backend/server/middleware.ts index 8e0b58df..06dc5a99 100644 --- a/src/backend/server/middleware.ts +++ b/src/backend/server/middleware.ts @@ -1,5 +1,5 @@ +import { Logger } from "@foxxmd/logging"; import { ExpressHandler } from "../common/infrastructure/Atomic.js"; -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 c1341c84..de398a79 100644 --- a/src/backend/server/plexRoutes.ts +++ b/src/backend/server/plexRoutes.ts @@ -1,10 +1,9 @@ +import { ExpressWithAsync } from "@awaitjs/express"; +import { childLogger, Logger } from "@foxxmd/logging"; import { ExpressHandler } from "../common/infrastructure/Atomic.js"; -import { mergeArr, parseBool } from "../utils.js"; -import {ExpressWithAsync} from "@awaitjs/express"; -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"; +import PlexSource, { plexRequestMiddle } from "../sources/PlexSource.js"; +import ScrobbleSources from "../sources/ScrobbleSources.js"; export const setupPlexRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { diff --git a/src/backend/server/tautulliRoutes.ts b/src/backend/server/tautulliRoutes.ts index cac0ba10..ae4f523d 100644 --- a/src/backend/server/tautulliRoutes.ts +++ b/src/backend/server/tautulliRoutes.ts @@ -1,10 +1,10 @@ -import { TautulliNotifier } from "../sources/ingressNotifiers/TautulliNotifier.js"; +import { ExpressWithAsync } from "@awaitjs/express"; +import { Logger } from "@foxxmd/logging"; import { ExpressHandler } from "../common/infrastructure/Atomic.js"; +import { TautulliNotifier } from "../sources/ingressNotifiers/TautulliNotifier.js"; +import ScrobbleSources from "../sources/ScrobbleSources.js"; import TautulliSource from "../sources/TautulliSource.js"; import { parseBool } from "../utils.js"; -import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/logging"; -import ScrobbleSources from "../sources/ScrobbleSources.js"; export const setupTautulliRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { diff --git a/src/backend/server/webscrobblerRoutes.ts b/src/backend/server/webscrobblerRoutes.ts index 18b4956c..cbc343ee 100644 --- a/src/backend/server/webscrobblerRoutes.ts +++ b/src/backend/server/webscrobblerRoutes.ts @@ -1,13 +1,11 @@ -import { mergeArr, parseBool, remoteHostIdentifiers } from "../utils.js"; -import {ExpressWithAsync} from "@awaitjs/express"; -import {childLogger, Logger} from "@foxxmd/logging"; -import ScrobbleSources from "../sources/ScrobbleSources.js"; +import { ExpressWithAsync } from "@awaitjs/express"; +import { childLogger, Logger } from "@foxxmd/logging"; import bodyParser from "body-parser"; -import { WebScrobblerPayload } from "../common/vendor/webscrobbler/interfaces.js"; +import path from "path"; import { WebhookNotifier } from "../sources/ingressNotifiers/WebhookNotifier.js"; -import { nonEmptyBody } from "./middleware.js"; +import ScrobbleSources from "../sources/ScrobbleSources.js"; import { WebScrobblerSource } from "../sources/WebScrobblerSource.js"; -import path from "path"; +import { nonEmptyBody } from "./middleware.js"; export const setupWebscrobblerRoutes = (app: ExpressWithAsync, parentLogger: Logger, scrobbleSources: ScrobbleSources) => { diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index 915f16e6..ed03b8f2 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -1,16 +1,10 @@ -import dayjs, {Dayjs} from "dayjs"; -import { - genGroupId, - genGroupIdStrFromPlay, - mergeArr, - playObjDataMatch, - pollingBackoff, - sleep, - sortByNewestPlayDate, - sortByOldestPlayDate, - findCauseByFunc, - formatNumber, -} from "../utils.js"; +import { childLogger, Logger } from '@foxxmd/logging'; +import dayjs, { Dayjs } from "dayjs"; +import { EventEmitter } from "events"; +import { FixedSizeList } from "fixed-size-list"; +import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; +import { buildTrackString, capitalize } from "../../core/StringUtils.js"; +import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; import { Authenticatable, DEFAULT_POLLING_INTERVAL, @@ -18,9 +12,7 @@ import { DEFAULT_RETRY_MULTIPLIER, DeviceId, GroupedFixedPlays, - GroupedPlays, InternalConfig, - NO_DEVICE, NO_USER, PlayPlatformId, PlayUserId, @@ -28,14 +20,18 @@ import { SINGLE_USER_PLATFORM_ID, SourceType, } from "../common/infrastructure/Atomic.js"; -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"; import TupleMap from "../common/TupleMap.js"; -import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; -import { buildTrackString, capitalize } from "../../core/StringUtils.js"; -import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; +import { + findCauseByFunc, + formatNumber, + genGroupId, + playObjDataMatch, + pollingBackoff, + sleep, + sortByNewestPlayDate, + sortByOldestPlayDate, +} from "../utils.js"; import { comparePlayTemporally, temporalAccuracyIsAtLeast } from "../utils/TimeUtils.js"; export interface RecentlyPlayedOptions { diff --git a/src/backend/sources/ChromecastSource.ts b/src/backend/sources/ChromecastSource.ts index c0f08dd2..1745ba3b 100644 --- a/src/backend/sources/ChromecastSource.ts +++ b/src/backend/sources/ChromecastSource.ts @@ -1,5 +1,14 @@ -import MemorySource from "./MemorySource.js"; -import { ChromecastSourceConfig } from "../common/infrastructure/config/source/chromecast.js"; +import { createPlatform, Media, MediaController, PersistentClient } from "@foxxmd/chromecast-client"; +import { ContextualValidationError } from "@foxxmd/chromecast-client/dist/cjs/src/utils.js"; +import { childLogger, Logger } from "@foxxmd/logging"; +import { Client as CastClient } from 'castv2'; +import dayjs from "dayjs"; +import { EventEmitter } from "events"; +import e from "express"; +import { findCauseByReference } from "pony-cause"; +import { PlayObject } from "../../core/Atomic.js"; +import { buildTrackString } from "../../core/StringUtils.js"; +import { NETWORK_ERROR_FAILURE_CODES } from "../common/errors/NodeErrors.js"; import { FormatPlayObjectOptions, InternalConfig, @@ -8,30 +17,24 @@ import { PlayerStateData, SourceData, } from "../common/infrastructure/Atomic.js"; -import {EventEmitter} from "events"; -import {MediaController, PersistentClient, Media, createPlatform} from "@foxxmd/chromecast-client"; -import {Client as CastClient} from 'castv2'; -import {findCauseByReference} from "pony-cause"; -import { PlayObject } from "../../core/Atomic.js"; -import dayjs from "dayjs"; -import { RecentlyPlayedOptions } from "./AbstractSource.js"; -import { difference, genGroupIdStr, isIPv4, mergeArr, parseBool, sleep } from "../utils.js"; -import { PlatformApplication, PlatformApplicationWithContext, PlatformType } from "../common/vendor/chromecast/interfaces.js"; +import { ChromecastSourceConfig } from "../common/infrastructure/config/source/chromecast.js"; +import { MaybeLogger } from "../common/logging.js"; import { chromePlayerStateToReported, genDeviceId, + genPlayHash, getCurrentPlatformApplications, getMediaStatus, - genPlayHash, } from "../common/vendor/chromecast/ChromecastClientUtils.js"; -import {Logger} from "@foxxmd/logging"; -import {ContextualValidationError} from "@foxxmd/chromecast-client/dist/cjs/src/utils.js"; -import { buildTrackString } from "../../core/StringUtils.js"; +import { + PlatformApplication, + PlatformApplicationWithContext, + PlatformType +} from "../common/vendor/chromecast/interfaces.js"; +import { difference, genGroupIdStr, parseBool } from "../utils.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"; +import { RecentlyPlayedOptions } from "./AbstractSource.js"; +import MemorySource from "./MemorySource.js"; interface ChromecastDeviceInfo { mdns: MdnsDeviceInfo diff --git a/src/backend/sources/DeezerSource.ts b/src/backend/sources/DeezerSource.ts index 22c3d8db..7c95cd63 100644 --- a/src/backend/sources/DeezerSource.ts +++ b/src/backend/sources/DeezerSource.ts @@ -1,19 +1,13 @@ -import request from 'superagent'; -import passport from "passport"; -import { - parseRetryAfterSecsFromObj, - readJson, - sleep, - sortByOldestPlayDate, - writeFile, -} from "../utils.js"; -import {Strategy as DeezerStrategy} from 'passport-deezer'; -import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; import dayjs from "dayjs"; -import { DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; -import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; import EventEmitter from "events"; +import passport from "passport"; +import { Strategy as DeezerStrategy } from 'passport-deezer'; +import request from 'superagent'; import { PlayObject } from "../../core/Atomic.js"; +import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; +import { DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; +import { parseRetryAfterSecsFromObj, readJson, sleep, sortByOldestPlayDate, writeFile, } from "../utils.js"; +import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; export default class DeezerSource extends AbstractSource { workingCredsPath; diff --git a/src/backend/sources/JRiverSource.ts b/src/backend/sources/JRiverSource.ts index a0d720dd..526eff2f 100644 --- a/src/backend/sources/JRiverSource.ts +++ b/src/backend/sources/JRiverSource.ts @@ -1,13 +1,13 @@ -import MemorySource from "./MemorySource.js"; -import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; import dayjs from "dayjs"; -import {URL} from "url"; +import { EventEmitter } from "events"; import normalizeUrl from 'normalize-url'; -import {EventEmitter} from "events"; -import { RecentlyPlayedOptions } from "./AbstractSource.js"; +import { URL } from "url"; +import { PlayObject } from "../../core/Atomic.js"; +import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; import { JRiverSourceConfig } from "../common/infrastructure/config/source/jriver.js"; import { Info, JRiverApiClient, PLAYER_STATE } from "../common/vendor/JRiverApiClient.js"; -import { PlayObject } from "../../core/Atomic.js"; +import { RecentlyPlayedOptions } from "./AbstractSource.js"; +import MemorySource from "./MemorySource.js"; export class JRiverSource extends MemorySource { declare config: JRiverSourceConfig; diff --git a/src/backend/sources/JellyfinSource.ts b/src/backend/sources/JellyfinSource.ts index 840dead8..a7cb7e3d 100644 --- a/src/backend/sources/JellyfinSource.ts +++ b/src/backend/sources/JellyfinSource.ts @@ -1,26 +1,25 @@ -import MemorySource from "./MemorySource.js"; -import dayjs, {Dayjs} from "dayjs"; +import { Logger } from "@foxxmd/logging"; +import dayjs from "dayjs"; +import EventEmitter from "events"; +import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; +import { buildTrackString, splitByFirstFound, truncateStringToLength } from "../../core/StringUtils.js"; +import { FormatPlayObjectOptions, InternalConfig, PlayPlatformId } from "../common/infrastructure/Atomic.js"; +import { JellySourceConfig } from "../common/infrastructure/config/source/jellyfin.js"; import { combinePartsToString, + doubleReturnNewline, parseBool, parseDurationFromTimestamp, playObjDataMatch, - doubleReturnNewline, } from "../utils.js"; -import { JellySourceConfig } from "../common/infrastructure/config/source/jellyfin.js"; -import { FormatPlayObjectOptions, InternalConfig, PlayPlatformId } from "../common/infrastructure/Atomic.js"; -import EventEmitter from "events"; -import { PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; -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"; -import {source} from "common-tags"; import { comparePlayTemporally, temporalAccuracyIsAtLeast, temporalPlayComparisonSummary, } from "../utils/TimeUtils.js"; +import MemorySource from "./MemorySource.js"; +import { PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; +import { JellyfinPlayerState } from "./PlayerState/JellyfinPlayerState.js"; const shortDeviceId = truncateStringToLength(10, ''); diff --git a/src/backend/sources/KodiSource.ts b/src/backend/sources/KodiSource.ts index b6329f95..ca58cb19 100644 --- a/src/backend/sources/KodiSource.ts +++ b/src/backend/sources/KodiSource.ts @@ -1,10 +1,10 @@ -import MemorySource from "./MemorySource.js"; +import { EventEmitter } from "events"; +import { PlayObject } from "../../core/Atomic.js"; import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; -import {EventEmitter} from "events"; -import { RecentlyPlayedOptions } from "./AbstractSource.js"; import { KodiSourceConfig } from "../common/infrastructure/config/source/kodi.js"; import { KodiApiClient } from "../common/vendor/KodiApiClient.js"; -import { PlayObject } from "../../core/Atomic.js"; +import { RecentlyPlayedOptions } from "./AbstractSource.js"; +import MemorySource from "./MemorySource.js"; export class KodiSource extends MemorySource { declare config: KodiSourceConfig; diff --git a/src/backend/sources/LastfmSource.ts b/src/backend/sources/LastfmSource.ts index 9dc631a1..0c16b903 100644 --- a/src/backend/sources/LastfmSource.ts +++ b/src/backend/sources/LastfmSource.ts @@ -1,15 +1,15 @@ -import { RecentlyPlayedOptions } from "./AbstractSource.js"; +import dayjs from "dayjs"; +import EventEmitter from "events"; +import { TrackObject, UserGetRecentTracksResponse } from "lastfm-node-client"; +import request from "superagent"; +import { PlayObject, SOURCE_SOT } from "../../core/Atomic.js"; +import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; +import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; +import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; import LastfmApiClient from "../common/vendor/LastfmApiClient.js"; import { sortByOldestPlayDate } from "../utils.js"; -import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; -import {TrackObject, UserGetRecentTracksResponse} from "lastfm-node-client"; -import EventEmitter from "events"; -import {PlayObject, SOURCE_SOT} from "../../core/Atomic.js"; +import { RecentlyPlayedOptions } from "./AbstractSource.js"; import MemorySource from "./MemorySource.js"; -import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; -import dayjs from "dayjs"; -import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import request from "superagent"; export default class LastfmSource extends MemorySource { diff --git a/src/backend/sources/ListenbrainzSource.ts b/src/backend/sources/ListenbrainzSource.ts index 0ef9b53e..3995437c 100644 --- a/src/backend/sources/ListenbrainzSource.ts +++ b/src/backend/sources/ListenbrainzSource.ts @@ -1,12 +1,12 @@ -import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; -import { FormatPlayObjectOptions, INITIALIZING, InternalConfig } from "../common/infrastructure/Atomic.js"; import EventEmitter from "events"; +import request from "superagent"; +import { PlayObject, SOURCE_SOT } from "../../core/Atomic.js"; +import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; +import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz.js"; import { ListenbrainzApiClient } from "../common/vendor/ListenbrainzApiClient.js"; +import { RecentlyPlayedOptions } from "./AbstractSource.js"; import MemorySource from "./MemorySource.js"; -import request from "superagent"; -import {isNodeNetworkException} from "../common/errors/NodeErrors.js"; -import {PlayObject, SOURCE_SOT} from "../../core/Atomic.js"; export default class ListenbrainzSource extends MemorySource { diff --git a/src/backend/sources/MPRISSource.ts b/src/backend/sources/MPRISSource.ts index 0ace261c..37647900 100644 --- a/src/backend/sources/MPRISSource.ts +++ b/src/backend/sources/MPRISSource.ts @@ -1,4 +1,9 @@ +import { Interfaces as Notifications } from '@dbus-types/notifications' import dayjs from "dayjs"; +import { DBusInterface, sessionBus } from 'dbus-ts'; +import EventEmitter from "events"; +import { PlayObject } from "../../core/Atomic.js"; +import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; import { MPRIS_IFACE, MPRIS_PATH, @@ -8,14 +13,9 @@ import { PlaybackStatus, PlayerInfo, } from "../common/infrastructure/config/source/mpris.js"; -import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; -import MemorySource from "./MemorySource.js"; -import { RecentlyPlayedOptions } from "./AbstractSource.js"; import { removeDuplicates } from "../utils.js"; -import EventEmitter from "events"; -import { PlayObject } from "../../core/Atomic.js"; -import {DBusInterface, sessionBus} from 'dbus-ts'; -import { Interfaces as Notifications } from '@dbus-types/notifications' +import { RecentlyPlayedOptions } from "./AbstractSource.js"; +import MemorySource from "./MemorySource.js"; export class MPRISSource extends MemorySource { diff --git a/src/backend/sources/MemorySource.ts b/src/backend/sources/MemorySource.ts index 9c75dfbd..196db4c6 100644 --- a/src/backend/sources/MemorySource.ts +++ b/src/backend/sources/MemorySource.ts @@ -1,41 +1,33 @@ -import AbstractSource from "./AbstractSource.js"; -import { - playObjDataMatch, - sortByOldestPlayDate, - toProgressAwarePlayObject, - getProgress, - thresholdResultSummary, - genGroupId, - genGroupIdStr, - getPlatformIdFromData, - formatNumber, -} from "../utils.js"; +import { Logger } from "@foxxmd/logging"; import dayjs from "dayjs"; +import { EventEmitter } from "events"; +import objectHash from 'object-hash'; +import { SimpleIntervalJob, Task, ToadScheduler } from "toad-scheduler"; +import { PlayObject, SOURCE_SOT, SOURCE_SOT_TYPES, SourcePlayerObj } from "../../core/Atomic.js"; +import { buildTrackString } from "../../core/StringUtils.js"; import { asPlayerStateData, CALCULATED_PLAYER_STATUSES, - DeviceId, - GroupedPlays, InternalConfig, PlayerStateData, PlayPlatformId, - PlayUserId, ProgressAwarePlayObject, - ScrobbleThresholdResult, SourceType, } from "../common/infrastructure/Atomic.js"; -import TupleMap from "../common/TupleMap.js"; -import {AbstractPlayerState, createPlayerOptions, PlayerStateOptions} from "./PlayerState/AbstractPlayerState.js"; -import { GenericPlayerState } from "./PlayerState/GenericPlayerState.js"; -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"; +import { PollingOptions } from "../common/infrastructure/config/common.js"; import { SourceConfig } from "../common/infrastructure/config/source/sources.js"; -import {EventEmitter} from "events"; -import objectHash from 'object-hash'; +import { + formatNumber, + genGroupId, + genGroupIdStr, + getPlatformIdFromData, + playObjDataMatch, + thresholdResultSummary, +} from "../utils.js"; import { timePassesScrobbleThreshold } from "../utils/TimeUtils.js"; -import {PollingOptions} from "../common/infrastructure/config/common.js"; +import AbstractSource from "./AbstractSource.js"; +import { AbstractPlayerState, createPlayerOptions, PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; +import { GenericPlayerState } from "./PlayerState/GenericPlayerState.js"; export default class MemorySource extends AbstractSource { diff --git a/src/backend/sources/MopidySource.ts b/src/backend/sources/MopidySource.ts index 0215e8ee..369671f3 100644 --- a/src/backend/sources/MopidySource.ts +++ b/src/backend/sources/MopidySource.ts @@ -1,21 +1,21 @@ -import MemorySource from "./MemorySource.js"; -import { MopidySourceConfig } from "../common/infrastructure/config/source/mopidy.js"; +import { loggerTest } from "@foxxmd/logging"; +import dayjs from "dayjs"; +import { EventEmitter } from "events"; +import Mopidy, { models } from "mopidy"; +import normalizeUrl from 'normalize-url'; +import pEvent from 'p-event'; +import { URL } from "url"; +import { PlayObject } from "../../core/Atomic.js"; +import { buildTrackString } from "../../core/StringUtils.js"; import { FormatPlayObjectOptions, InternalConfig, PlayerStateData, SINGLE_USER_PLATFORM_ID, } from "../common/infrastructure/Atomic.js"; -import dayjs from "dayjs"; -import Mopidy, {models} from "mopidy"; -import {URL} from "url"; -import normalizeUrl from 'normalize-url'; -import {EventEmitter} from "events"; -import pEvent from 'p-event'; +import { MopidySourceConfig } from "../common/infrastructure/config/source/mopidy.js"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; -import { PlayObject } from "../../core/Atomic.js"; -import { buildTrackString } from "../../core/StringUtils.js"; -import {loggerTest} from "@foxxmd/logging"; +import MemorySource from "./MemorySource.js"; export class MopidySource extends MemorySource { declare config: MopidySourceConfig; diff --git a/src/backend/sources/PlayerState/AbstractPlayerState.ts b/src/backend/sources/PlayerState/AbstractPlayerState.ts index 5db90442..787654d8 100644 --- a/src/backend/sources/PlayerState/AbstractPlayerState.ts +++ b/src/backend/sources/PlayerState/AbstractPlayerState.ts @@ -1,3 +1,7 @@ +import { childLogger, Logger } from "@foxxmd/logging"; +import dayjs, { Dayjs } from "dayjs"; +import { PlayObject, Second, SOURCE_SOT, SOURCE_SOT_TYPES, SourcePlayerObj } from "../../../core/Atomic.js"; +import { buildTrackString } from "../../../core/StringUtils.js"; import { CALCULATED_PLAYER_STATUSES, CalculatedPlayerStatus, @@ -5,15 +9,10 @@ import { REPORTED_PLAYER_STATUSES, ReportedPlayerStatus, } from "../../common/infrastructure/Atomic.js"; -import dayjs, {Dayjs} from "dayjs"; +import { PollingOptions } from "../../common/infrastructure/config/common.js"; import { formatNumber, genGroupIdStr, playObjDataMatch, progressBar } from "../../utils.js"; -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"; import { ListenRange } from "./ListenRange.js"; -import {id} from "common-tags"; -import {PollingOptions} from "../../common/infrastructure/config/common.js"; export interface PlayerStateIntervals { staleInterval?: number diff --git a/src/backend/sources/PlayerState/GenericPlayerState.ts b/src/backend/sources/PlayerState/GenericPlayerState.ts index 3977de6a..bafb6b5f 100644 --- a/src/backend/sources/PlayerState/GenericPlayerState.ts +++ b/src/backend/sources/PlayerState/GenericPlayerState.ts @@ -1,6 +1,6 @@ -import { AbstractPlayerState, PlayerStateOptions } from "./AbstractPlayerState.js"; -import {Logger} from "@foxxmd/logging"; +import { Logger } from "@foxxmd/logging"; import { PlayPlatformId } from "../../common/infrastructure/Atomic.js"; +import { AbstractPlayerState, PlayerStateOptions } from "./AbstractPlayerState.js"; export class GenericPlayerState extends AbstractPlayerState { constructor(logger: Logger, platformId: PlayPlatformId, opts?: PlayerStateOptions) { diff --git a/src/backend/sources/PlayerState/JellyfinPlayerState.ts b/src/backend/sources/PlayerState/JellyfinPlayerState.ts index b7019cde..764608b2 100644 --- a/src/backend/sources/PlayerState/JellyfinPlayerState.ts +++ b/src/backend/sources/PlayerState/JellyfinPlayerState.ts @@ -1,8 +1,8 @@ -import { GenericPlayerState } from "./GenericPlayerState.js"; -import {Logger} from "@foxxmd/logging"; +import { Logger } from "@foxxmd/logging"; +import { PlayObject } from "../../../core/Atomic.js"; import { PlayPlatformId, ReportedPlayerStatus } from "../../common/infrastructure/Atomic.js"; import { PlayerStateOptions } from "./AbstractPlayerState.js"; -import { PlayObject } from "../../../core/Atomic.js"; +import { GenericPlayerState } from "./GenericPlayerState.js"; export class JellyfinPlayerState extends GenericPlayerState { constructor(logger: Logger, platformId: PlayPlatformId, opts?: PlayerStateOptions) { diff --git a/src/backend/sources/PlayerState/ListenProgress.ts b/src/backend/sources/PlayerState/ListenProgress.ts index a36667ba..2d66e58e 100644 --- a/src/backend/sources/PlayerState/ListenProgress.ts +++ b/src/backend/sources/PlayerState/ListenProgress.ts @@ -1,4 +1,4 @@ -import dayjs, {Dayjs} from "dayjs"; +import dayjs, { Dayjs } from "dayjs"; import { PlayProgress, Second } from "../../../core/Atomic.js"; diff --git a/src/backend/sources/PlayerState/ListenRange.ts b/src/backend/sources/PlayerState/ListenRange.ts index 6b10d40f..6a2dc050 100644 --- a/src/backend/sources/PlayerState/ListenRange.ts +++ b/src/backend/sources/PlayerState/ListenRange.ts @@ -1,6 +1,6 @@ -import { ListenProgress } from "./ListenProgress.js"; -import dayjs, {Dayjs} from "dayjs"; +import dayjs, { Dayjs } from "dayjs"; import { ListenRangeData, Second } from "../../../core/Atomic.js"; +import { ListenProgress } from "./ListenProgress.js"; export class ListenRange implements ListenRangeData { diff --git a/src/backend/sources/PlexSource.ts b/src/backend/sources/PlexSource.ts index a5697305..703caddc 100644 --- a/src/backend/sources/PlexSource.ts +++ b/src/backend/sources/PlexSource.ts @@ -1,14 +1,14 @@ -import dayjs from "dayjs"; -import { combinePartsToString } from "../utils.js"; -import AbstractSource from "./AbstractSource.js"; -import formidable from 'formidable'; +import { childLogger, Logger } from "@foxxmd/logging"; import concatStream from 'concat-stream'; -import { PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; -import { FormatPlayObjectOptions, InternalConfig, SourceType } from "../common/infrastructure/Atomic.js"; +import dayjs from "dayjs"; import EventEmitter from "events"; +import formidable from 'formidable'; import { PlayObject } from "../../core/Atomic.js"; import { truncateStringToLength } from "../../core/StringUtils.js"; -import {childLogger, Logger} from "@foxxmd/logging"; +import { FormatPlayObjectOptions, InternalConfig, SourceType } from "../common/infrastructure/Atomic.js"; +import { PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; +import { combinePartsToString } from "../utils.js"; +import AbstractSource from "./AbstractSource.js"; const shortDeviceId = truncateStringToLength(10, ''); diff --git a/src/backend/sources/ScrobbleSources.ts b/src/backend/sources/ScrobbleSources.ts index 13ed28ae..70d72f5b 100644 --- a/src/backend/sources/ScrobbleSources.ts +++ b/src/backend/sources/ScrobbleSources.ts @@ -1,46 +1,45 @@ /* eslint-disable no-case-declarations */ -import { mergeArr, parseBool, readJson, validateJson } from "../utils.js"; -import SpotifySource from "./SpotifySource.js"; -import PlexSource from "./PlexSource.js"; -import TautulliSource from "./TautulliSource.js"; -import { SubsonicSource } from "./SubsonicSource.js"; -import JellyfinSource from "./JellyfinSource.js"; -import LastfmSource from "./LastfmSource.js"; -import DeezerSource from "./DeezerSource.js"; -import { ConfigMeta, InternalConfig, SourceType, sourceTypes } from "../common/infrastructure/Atomic.js"; +import { childLogger, Logger } from '@foxxmd/logging'; +import EventEmitter from "events"; import { configDir as defaultConfigDir } from "../common/index.js"; -import {childLogger, Logger} from '@foxxmd/logging'; -import { SourceAIOConfig, SourceConfig } from "../common/infrastructure/config/source/sources.js"; +import { ConfigMeta, InternalConfig, SourceType, sourceTypes } from "../common/infrastructure/Atomic.js"; +import { AIOConfig, SourceDefaults } from "../common/infrastructure/config/aioConfig.js"; +import { ChromecastSourceConfig } from "../common/infrastructure/config/source/chromecast.js"; import { DeezerData, DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; -import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; import { JellyData, JellySourceConfig } from "../common/infrastructure/config/source/jellyfin.js"; -import { SubsonicData, SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js"; -import { TautulliSourceConfig } from "../common/infrastructure/config/source/tautulli.js"; +import { JRiverData, JRiverSourceConfig } from "../common/infrastructure/config/source/jriver.js"; +import { KodiData, KodiSourceConfig } from "../common/infrastructure/config/source/kodi.js"; +import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; +import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz.js"; +import { MopidySourceConfig } from "../common/infrastructure/config/source/mopidy.js"; +import { MPRISData, MPRISSourceConfig } from "../common/infrastructure/config/source/mpris.js"; import { PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; +import { SourceAIOConfig, SourceConfig } from "../common/infrastructure/config/source/sources.js"; import { SpotifySourceConfig, SpotifySourceData } from "../common/infrastructure/config/source/spotify.js"; -import AbstractSource from "./AbstractSource.js"; -import { AIOConfig, SourceDefaults } from "../common/infrastructure/config/aioConfig.js"; +import { SubsonicData, SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js"; +import { TautulliSourceConfig } from "../common/infrastructure/config/source/tautulli.js"; +import { WebScrobblerSourceConfig } from "../common/infrastructure/config/source/webscrobbler.js"; +import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; import * as aioSchema from "../common/schema/aio-source.json"; import * as sourceSchema from "../common/schema/source.json"; -import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; -import YTMusicSource from "./YTMusicSource.js"; -import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; -import { MPRISData, MPRISSourceConfig } from "../common/infrastructure/config/source/mpris.js"; -import { MPRISSource } from "./MPRISSource.js"; -import EventEmitter from "events"; -import { MopidySource } from "./MopidySource.js"; -import { MopidySourceConfig } from "../common/infrastructure/config/source/mopidy.js"; -import ListenbrainzSource from "./ListenbrainzSource.js"; -import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz.js"; +import { WildcardEmitter } from "../common/WildcardEmitter.js"; +import { parseBool, readJson, validateJson } from "../utils.js"; +import AbstractSource from "./AbstractSource.js"; +import { ChromecastSource } from "./ChromecastSource.js"; +import DeezerSource from "./DeezerSource.js"; +import JellyfinSource from "./JellyfinSource.js"; import { JRiverSource } from "./JRiverSource.js"; -import { JRiverData, JRiverSourceConfig } from "../common/infrastructure/config/source/jriver.js"; import { KodiSource } from "./KodiSource.js"; -import { KodiData, KodiSourceConfig } from "../common/infrastructure/config/source/kodi.js"; -import { WildcardEmitter } from "../common/WildcardEmitter.js"; +import LastfmSource from "./LastfmSource.js"; +import ListenbrainzSource from "./ListenbrainzSource.js"; +import { MopidySource } from "./MopidySource.js"; +import { MPRISSource } from "./MPRISSource.js"; +import PlexSource from "./PlexSource.js"; +import SpotifySource from "./SpotifySource.js"; +import { SubsonicSource } from "./SubsonicSource.js"; +import TautulliSource from "./TautulliSource.js"; import { WebScrobblerSource } from "./WebScrobblerSource.js"; -import { WebScrobblerSourceConfig } from "../common/infrastructure/config/source/webscrobbler.js"; -import { ChromecastSource } from "./ChromecastSource.js"; -import { ChromecastSourceConfig } from "../common/infrastructure/config/source/chromecast.js"; +import YTMusicSource from "./YTMusicSource.js"; type groupedNamedConfigs = {[key: string]: ParsedConfig[]}; diff --git a/src/backend/sources/SpotifySource.ts b/src/backend/sources/SpotifySource.ts index acac7c9e..aff68d14 100644 --- a/src/backend/sources/SpotifySource.ts +++ b/src/backend/sources/SpotifySource.ts @@ -1,17 +1,11 @@ -import dayjs, {Dayjs} from "dayjs"; -import { - readJson, - writeFile, - sortByOldestPlayDate, - sleep, - parseRetryAfterSecsFromObj, - combinePartsToString, - findCauseByFunc, -} from "../utils.js"; +import dayjs, { Dayjs } from "dayjs"; +import EventEmitter from "events"; import SpotifyWebApi from "spotify-web-api-node"; import request from 'superagent'; -import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; -import { SpotifySourceConfig } from "../common/infrastructure/config/source/spotify.js"; +import { PlayObject, SCROBBLE_TS_SOC_END, SCROBBLE_TS_SOC_START, ScrobbleTsSOC } from "../../core/Atomic.js"; +import { truncateStringToLength } from "../../core/StringUtils.js"; +import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; +import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; import { DEFAULT_POLLING_INTERVAL, FormatPlayObjectOptions, @@ -22,18 +16,24 @@ import { ReportedPlayerStatus, SourceData, } from "../common/infrastructure/Atomic.js"; -import PlayHistoryObject = SpotifyApi.PlayHistoryObject; -import EventEmitter from "events"; +import { SpotifySourceConfig } from "../common/infrastructure/config/source/spotify.js"; +import { + combinePartsToString, + findCauseByFunc, + parseRetryAfterSecsFromObj, + readJson, + sleep, + sortByOldestPlayDate, + writeFile, +} from "../utils.js"; +import { RecentlyPlayedOptions } from "./AbstractSource.js"; +import MemorySource from "./MemorySource.js"; +import AlbumObjectSimplified = SpotifyApi.AlbumObjectSimplified; +import ArtistObjectSimplified = SpotifyApi.ArtistObjectSimplified; import CurrentlyPlayingObject = SpotifyApi.CurrentlyPlayingObject; +import PlayHistoryObject = SpotifyApi.PlayHistoryObject; import TrackObjectFull = SpotifyApi.TrackObjectFull; -import ArtistObjectSimplified = SpotifyApi.ArtistObjectSimplified; -import AlbumObjectSimplified = SpotifyApi.AlbumObjectSimplified; import UserDevice = SpotifyApi.UserDevice; -import MemorySource from "./MemorySource.js"; -import { PlayObject, SCROBBLE_TS_SOC_END, SCROBBLE_TS_SOC_START, ScrobbleTsSOC } from "../../core/Atomic.js"; -import { buildTrackString, truncateStringToLength } from "../../core/StringUtils.js"; -import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; -import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; const scopes = ['user-read-recently-played', 'user-read-currently-playing', 'user-read-playback-state', 'user-read-playback-position']; const state = 'random'; diff --git a/src/backend/sources/SubsonicSource.ts b/src/backend/sources/SubsonicSource.ts index bee2dc42..73c6ff58 100644 --- a/src/backend/sources/SubsonicSource.ts +++ b/src/backend/sources/SubsonicSource.ts @@ -1,17 +1,17 @@ -import request, {Request, Response} from 'superagent'; import * as crypto from 'crypto'; import dayjs from "dayjs"; import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js"; -import {findCauseByFunc, parseRetryAfterSecsFromObj, removeDuplicates, sleep} from "../utils.js"; -import MemorySource from "./MemorySource.js"; -import { SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js"; -import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; -import { RecentlyPlayedOptions } from "./AbstractSource.js"; import EventEmitter from "events"; +import request, { Request } from 'superagent'; import { PlayObject } from "../../core/Atomic.js"; -import {isNodeNetworkException} from "../common/errors/NodeErrors.js"; -import {UpstreamError} from "../common/errors/UpstreamError.js"; -import {getSubsonicResponse, SubsonicResponse, SubsonicResponseCommon} from "../common/vendor/subsonic/interfaces.js"; +import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; +import { UpstreamError } from "../common/errors/UpstreamError.js"; +import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; +import { SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js"; +import { getSubsonicResponse, SubsonicResponse, SubsonicResponseCommon } from "../common/vendor/subsonic/interfaces.js"; +import { findCauseByFunc, parseRetryAfterSecsFromObj, removeDuplicates, sleep } from "../utils.js"; +import { RecentlyPlayedOptions } from "./AbstractSource.js"; +import MemorySource from "./MemorySource.js"; dayjs.extend(isSameOrAfter); diff --git a/src/backend/sources/TautulliSource.ts b/src/backend/sources/TautulliSource.ts index ec2ffedb..86115480 100644 --- a/src/backend/sources/TautulliSource.ts +++ b/src/backend/sources/TautulliSource.ts @@ -1,12 +1,12 @@ import dayjs from "dayjs"; -import PlexSource from "./PlexSource.js"; -import { TautulliSourceConfig } from "../common/infrastructure/config/source/tautulli.js"; -import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; -import { combinePartsToString } from "../utils.js"; import EventEmitter from "events"; -import {Request} from "express"; +import { Request } from "express"; import { PlayObject } from "../../core/Atomic.js"; import { truncateStringToLength } from "../../core/StringUtils.js"; +import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; +import { TautulliSourceConfig } from "../common/infrastructure/config/source/tautulli.js"; +import { combinePartsToString } from "../utils.js"; +import PlexSource from "./PlexSource.js"; const shortDeviceId = truncateStringToLength(10, ''); diff --git a/src/backend/sources/WebScrobblerSource.ts b/src/backend/sources/WebScrobblerSource.ts index c22d39ea..023100dc 100644 --- a/src/backend/sources/WebScrobblerSource.ts +++ b/src/backend/sources/WebScrobblerSource.ts @@ -1,4 +1,6 @@ -import MemorySource from "./MemorySource.js"; +import dayjs from "dayjs"; +import EventEmitter from "events"; +import { PlayObject, SOURCE_SOT } from "../../core/Atomic.js"; import { FormatPlayObjectOptions, InternalConfig, @@ -7,11 +9,13 @@ import { REPORTED_PLAYER_STATUSES, ReportedPlayerStatus, } from "../common/infrastructure/Atomic.js"; -import EventEmitter from "events"; -import {PlayObject, SOURCE_SOT} from "../../core/Atomic.js"; -import { WebScrobblerHookEvent, WebScrobblerPayload, WebScrobblerSong } from "../common/vendor/webscrobbler/interfaces.js"; -import dayjs from "dayjs"; import { WebScrobblerSourceConfig } from "../common/infrastructure/config/source/webscrobbler.js"; +import { + WebScrobblerHookEvent, + WebScrobblerPayload, + WebScrobblerSong +} from "../common/vendor/webscrobbler/interfaces.js"; +import MemorySource from "./MemorySource.js"; export class WebScrobblerSource extends MemorySource { diff --git a/src/backend/sources/YTMusicSource.ts b/src/backend/sources/YTMusicSource.ts index 0a44f16b..ca1cba8e 100644 --- a/src/backend/sources/YTMusicSource.ts +++ b/src/backend/sources/YTMusicSource.ts @@ -1,13 +1,13 @@ -import YouTubeMusic from "youtube-music-ts-api"; -import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; -import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; -import {IYouTubeMusicAuthenticated} from "youtube-music-ts-api/interfaces-primary"; import dayjs from "dayjs"; -import { parseDurationFromTimestamp, playObjDataMatch } from "../utils.js"; -import {IPlaylistDetail, ITrackDetail} from "youtube-music-ts-api/interfaces-supplementary"; -import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; import EventEmitter from "events"; +import YouTubeMusic from "youtube-music-ts-api"; +import { IYouTubeMusicAuthenticated } from "youtube-music-ts-api/interfaces-primary"; +import { IPlaylistDetail, ITrackDetail } from "youtube-music-ts-api/interfaces-supplementary"; import { PlayObject } from "../../core/Atomic.js"; +import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; +import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; +import { parseDurationFromTimestamp, playObjDataMatch } from "../utils.js"; +import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; export default class YTMusicSource extends AbstractSource { apiInstance?: IYouTubeMusicAuthenticated diff --git a/src/backend/sources/ingressNotifiers/IngressNotifier.ts b/src/backend/sources/ingressNotifiers/IngressNotifier.ts index e5b6df72..c82d3f1f 100644 --- a/src/backend/sources/ingressNotifiers/IngressNotifier.ts +++ b/src/backend/sources/ingressNotifiers/IngressNotifier.ts @@ -1,7 +1,7 @@ -import {childLogger, Logger} from '@foxxmd/logging'; -import { remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; -import {Request} from "express"; +import { childLogger, Logger } from '@foxxmd/logging'; +import { Request } from "express"; import { RemoteIdentityParts } from "../../common/infrastructure/Atomic.js"; +import { remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; export abstract class IngressNotifier { diff --git a/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts b/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts index bc91ad69..e7d0cfa6 100644 --- a/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts +++ b/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts @@ -1,8 +1,8 @@ -import { IngressNotifier } from "./IngressNotifier.js"; -import {Request} from "express"; -import JellyfinSource from "../JellyfinSource.js"; +import { Logger } from "@foxxmd/logging"; +import { Request } from "express"; import { remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; -import {Logger} from "@foxxmd/logging"; +import JellyfinSource from "../JellyfinSource.js"; +import { IngressNotifier } from "./IngressNotifier.js"; export class JellyfinNotifier extends IngressNotifier { diff --git a/src/backend/sources/ingressNotifiers/PlexNotifier.ts b/src/backend/sources/ingressNotifiers/PlexNotifier.ts index 079e9c10..c12e1814 100644 --- a/src/backend/sources/ingressNotifiers/PlexNotifier.ts +++ b/src/backend/sources/ingressNotifiers/PlexNotifier.ts @@ -1,7 +1,7 @@ -import { IngressNotifier } from "./IngressNotifier.js"; -import {Request} from "express"; +import { Logger } from "@foxxmd/logging"; +import { Request } from "express"; import PlexSource from "../PlexSource.js"; -import {Logger} from "@foxxmd/logging"; +import { IngressNotifier } from "./IngressNotifier.js"; export class PlexNotifier extends IngressNotifier { diff --git a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts index 5db110a2..ccc5dac9 100644 --- a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts +++ b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts @@ -1,8 +1,7 @@ -import { IngressNotifier } from "./IngressNotifier.js"; -import {Request} from "express"; -import PlexSource from "../PlexSource.js"; +import { Logger } from "@foxxmd/logging"; +import { Request } from "express"; import TautulliSource from "../TautulliSource.js"; -import {Logger} from "@foxxmd/logging"; +import { IngressNotifier } from "./IngressNotifier.js"; export class TautulliNotifier extends IngressNotifier { diff --git a/src/backend/sources/ingressNotifiers/WebhookNotifier.ts b/src/backend/sources/ingressNotifiers/WebhookNotifier.ts index d5b22625..38e68388 100644 --- a/src/backend/sources/ingressNotifiers/WebhookNotifier.ts +++ b/src/backend/sources/ingressNotifiers/WebhookNotifier.ts @@ -1,7 +1,7 @@ -import { IngressNotifier } from "./IngressNotifier.js"; -import {Request} from "express"; +import { Logger } from "@foxxmd/logging"; +import { Request } from "express"; import path from "path"; -import {Logger} from "@foxxmd/logging"; +import { IngressNotifier } from "./IngressNotifier.js"; export class WebhookNotifier extends IngressNotifier { diff --git a/src/backend/tasks/heartbeatClients.ts b/src/backend/tasks/heartbeatClients.ts index 472fcd21..678210b3 100644 --- a/src/backend/tasks/heartbeatClients.ts +++ b/src/backend/tasks/heartbeatClients.ts @@ -1,7 +1,6 @@ -import {childLogger, Logger} from '@foxxmd/logging'; -import { mergeArr } from "../utils.js"; -import {AsyncTask} from "toad-scheduler"; -import {PromisePool} from "@supercharge/promise-pool"; +import { childLogger, Logger } from '@foxxmd/logging'; +import { PromisePool } from "@supercharge/promise-pool"; +import { AsyncTask } from "toad-scheduler"; import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; export const createHeartbeatClientsTask = (clients: ScrobbleClients, parentLogger: Logger) => { diff --git a/src/backend/tasks/heartbeatSources.ts b/src/backend/tasks/heartbeatSources.ts index 6d6efb9d..50ac72f0 100644 --- a/src/backend/tasks/heartbeatSources.ts +++ b/src/backend/tasks/heartbeatSources.ts @@ -1,9 +1,8 @@ -import {childLogger, Logger} from '@foxxmd/logging'; -import { mergeArr } from "../utils.js"; -import {AsyncTask} from "toad-scheduler"; -import {PromisePool} from "@supercharge/promise-pool"; -import ScrobbleSources from "../sources/ScrobbleSources.js"; +import { childLogger, Logger } from '@foxxmd/logging'; +import { PromisePool } from "@supercharge/promise-pool"; +import { AsyncTask } from "toad-scheduler"; import { ChromecastSource } from "../sources/ChromecastSource.js"; +import ScrobbleSources from "../sources/ScrobbleSources.js"; export const createHeartbeatSourcesTask = (sources: ScrobbleSources, parentLogger: Logger) => { const logger = childLogger(parentLogger, ['Heartbeat', 'Sources']); diff --git a/src/backend/tests/jellyfin/jellyfin.test.ts b/src/backend/tests/jellyfin/jellyfin.test.ts index 07770787..c1e7fdfd 100644 --- a/src/backend/tests/jellyfin/jellyfin.test.ts +++ b/src/backend/tests/jellyfin/jellyfin.test.ts @@ -1,11 +1,11 @@ -import {describe, it} from 'mocha'; -import {assert} from 'chai'; -import samplePayload from './playbackProgressSample.json'; +import { loggerTest } from "@foxxmd/logging"; +import { assert } from 'chai'; +import EventEmitter from "events"; +import { describe, it } from 'mocha'; +import { JsonPlayObject } from "../../../core/Atomic.js"; import JellyfinSource from "../../sources/JellyfinSource.js"; -import EventEmitter from "events"; -import { JsonPlayObject, PlayObject } from "../../../core/Atomic.js"; -import {loggerTest} from "@foxxmd/logging"; +import samplePayload from './playbackProgressSample.json'; const dataAsFixture = (data: any): TestFixture => { return data as TestFixture; diff --git a/src/backend/tests/listenbrainz/listenbrainz.test.ts b/src/backend/tests/listenbrainz/listenbrainz.test.ts index 8e35fe28..7f702421 100644 --- a/src/backend/tests/listenbrainz/listenbrainz.test.ts +++ b/src/backend/tests/listenbrainz/listenbrainz.test.ts @@ -1,11 +1,20 @@ -import {describe, it} from 'mocha'; -import {assert} from 'chai'; +import { loggerTest } from "@foxxmd/logging"; +import { assert } from 'chai'; +import dayjs from "dayjs"; +import { describe, it } from 'mocha'; +import { http, HttpResponse } from "msw"; +import { PlayObject } from "../../../core/Atomic.js"; +import { UpstreamError } from "../../common/errors/UpstreamError.js"; + +import { ListenbrainzApiClient, ListenResponse } from "../../common/vendor/ListenbrainzApiClient.js"; +import { ExpectedResults } from "../utils/interfaces.js"; +import { withRequestInterception } from "../utils/networking.js"; +import artistWithProperJoiner from './correctlyMapped/artistProperHasJoinerInName.json'; // correct mappings import multiArtistInArtistName from './correctlyMapped/multiArtistInArtistName.json'; import multiArtistsInTrackName from './correctlyMapped/multiArtistInTrackName.json'; -import noArtistMapping from './correctlyMapped/noArtistMapping.json'; import multiMappedArtistsWithSingleUserArtist from './correctlyMapped/multiArtistMappingWithSingleRecordedArtist.json'; -import artistWithProperJoiner from './correctlyMapped/artistProperHasJoinerInName.json'; +import noArtistMapping from './correctlyMapped/noArtistMapping.json'; import normalizedValues from './correctlyMapped/normalizedName.json'; import slightlyDifferentNames from './correctlyMapped/trackNameSlightlyDifferent.json'; @@ -13,15 +22,6 @@ import slightlyDifferentNames from './correctlyMapped/trackNameSlightlyDifferent import incorrectMultiArtistsTrackName from './incorrectlyMapped/multiArtistsInTrackName.json'; import veryWrong from './incorrectlyMapped/veryWrong.json'; -import { ListenbrainzApiClient, ListenResponse } from "../../common/vendor/ListenbrainzApiClient.js"; -import { PlayObject } from "../../../core/Atomic.js"; -import dayjs from "dayjs"; -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 expected: ExpectedResults diff --git a/src/backend/tests/player/player.test.ts b/src/backend/tests/player/player.test.ts index 178e3203..6584256d 100644 --- a/src/backend/tests/player/player.test.ts +++ b/src/backend/tests/player/player.test.ts @@ -1,12 +1,17 @@ -import {describe, it} from 'mocha'; -import {assert} from 'chai'; -import { generatePlay } from "../utils/PlayTestUtils.js"; +import { loggerTest } from "@foxxmd/logging"; +import { assert } from 'chai'; +import clone from "clone"; +import dayjs from "dayjs"; +import { describe, it } from 'mocha'; +import { + CALCULATED_PLAYER_STATUSES, + NO_DEVICE, + NO_USER, + REPORTED_PLAYER_STATUSES +} from "../../common/infrastructure/Atomic.js"; import { GenericPlayerState } from "../../sources/PlayerState/GenericPlayerState.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"; +import { generatePlay } from "../utils/PlayTestUtils.js"; const logger = loggerTest; diff --git a/src/backend/tests/scrobbler/TestScrobbler.ts b/src/backend/tests/scrobbler/TestScrobbler.ts index 15787e98..c6f3998d 100644 --- a/src/backend/tests/scrobbler/TestScrobbler.ts +++ b/src/backend/tests/scrobbler/TestScrobbler.ts @@ -1,9 +1,9 @@ -import AbstractScrobbleClient from "../../scrobblers/AbstractScrobbleClient.js"; -import { PlayObject } from "../../../core/Atomic.js"; -import { Notifiers } from "../../notifier/Notifiers.js"; +import { loggerTest } from "@foxxmd/logging"; import EventEmitter from "events"; import request from "superagent"; -import {loggerTest} from "@foxxmd/logging"; +import { PlayObject } from "../../../core/Atomic.js"; +import { Notifiers } from "../../notifier/Notifiers.js"; +import AbstractScrobbleClient from "../../scrobblers/AbstractScrobbleClient.js"; export class TestScrobbler extends AbstractScrobbleClient { diff --git a/src/backend/tests/scrobbler/scrobblers.test.ts b/src/backend/tests/scrobbler/scrobblers.test.ts index 29bab612..9cff7d8e 100644 --- a/src/backend/tests/scrobbler/scrobblers.test.ts +++ b/src/backend/tests/scrobbler/scrobblers.test.ts @@ -1,21 +1,20 @@ -import {describe, it, after, before} from 'mocha'; -import chai, {assert} from 'chai'; +import chai, { assert } from 'chai'; import asPromised from 'chai-as-promised'; import clone from 'clone'; -import pEvent from 'p-event'; +import dayjs from "dayjs"; +import { after, before, describe, it } from 'mocha'; import { http, HttpResponse } from 'msw'; - -chai.use(asPromised); - -import withDuration from '../plays/withDuration.json'; +import pEvent from 'p-event'; +import { PlayObject } from "../../../core/Atomic.js"; +import { sleep } from "../../utils.js"; import mixedDuration from '../plays/mixedDuration.json'; +import withDuration from '../plays/withDuration.json'; +import { MockNetworkError, withRequestInterception } from "../utils/networking.js"; +import { asPlays, generatePlay, normalizePlays } from "../utils/PlayTestUtils.js"; import { TestScrobbler } from "./TestScrobbler.js"; -import { asPlays, generatePlay, normalizePlays } from "../utils/PlayTestUtils.js"; -import dayjs from "dayjs"; -import { sleep } from "../../utils.js"; -import { MockNetworkError, withRequestInterception } from "../utils/networking.js"; -import { PlayObject } from "../../../core/Atomic.js"; + +chai.use(asPromised); const firstPlayDate = dayjs().subtract(1, 'hour'); const olderFirstPlayDate = dayjs().subtract(4, 'hour'); diff --git a/src/backend/tests/utils/PlayTestUtils.ts b/src/backend/tests/utils/PlayTestUtils.ts index f46ae56c..16f7bc87 100644 --- a/src/backend/tests/utils/PlayTestUtils.ts +++ b/src/backend/tests/utils/PlayTestUtils.ts @@ -1,11 +1,11 @@ -import { JsonPlayObject, ObjectPlayData, PlayData, PlayMeta, PlayObject } from "../../../core/Atomic.js"; -import dayjs, {Dayjs} from "dayjs"; -import utc from "dayjs/plugin/utc.js"; +import { faker } from '@faker-js/faker'; +import dayjs, { Dayjs } from "dayjs"; +import duration from "dayjs/plugin/duration.js"; import isBetween from "dayjs/plugin/isBetween.js"; import relativeTime from "dayjs/plugin/relativeTime.js"; -import duration from "dayjs/plugin/duration.js"; import timezone from "dayjs/plugin/timezone.js"; -import {faker} from '@faker-js/faker'; +import utc from "dayjs/plugin/utc.js"; +import { JsonPlayObject, ObjectPlayData, PlayMeta, PlayObject } from "../../../core/Atomic.js"; dayjs.extend(utc) dayjs.extend(isBetween); diff --git a/src/backend/tests/utils/networking.ts b/src/backend/tests/utils/networking.ts index 7818c8b4..8fe3ab21 100644 --- a/src/backend/tests/utils/networking.ts +++ b/src/backend/tests/utils/networking.ts @@ -1,5 +1,5 @@ +import { setupServer, SetupServer } from 'msw/node'; import { NodeNetworkErrorCode, NodeNetworkException } from "../../common/errors/NodeErrors.js"; -import { setupServer, SetupServer } from 'msw/node'; export class MockNetworkError extends Error implements NodeNetworkException { diff --git a/src/backend/tests/utils/strings.test.ts b/src/backend/tests/utils/strings.test.ts index a0693691..e30f4c80 100644 --- a/src/backend/tests/utils/strings.test.ts +++ b/src/backend/tests/utils/strings.test.ts @@ -1,14 +1,14 @@ -import {describe, it} from 'mocha'; -import {assert} from 'chai'; +import { assert } from 'chai'; +import { describe, it } from 'mocha'; +import { intersect } from "../../utils.js"; import { compareNormalizedStrings, normalizeStr, parseTrackCredits, uniqueNormalizedStrArr } from "../../utils/StringUtils.js"; -import testData from './playTestData.json'; import { ExpectedResults } from "./interfaces.js"; -import { intersect } from "../../utils.js"; +import testData from './playTestData.json'; interface PlayTestFixture { caseHints: string[] diff --git a/src/backend/utils.ts b/src/backend/utils.ts index c15be4bf..f3a280fc 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -1,12 +1,19 @@ -import {accessSync, constants, promises} from "fs"; -import dayjs, {Dayjs} from 'dayjs'; +import { Logger } from '@foxxmd/logging'; +import backoffStrategies from '@kenyip/backoff-strategies'; +import address from "address"; +import * as AjvNS from 'ajv'; +import Ajv, { Schema } from 'ajv'; +import { replaceResultTransformer, stripIndentTransformer, TemplateTag, trimResultTransformer } from 'common-tags'; +import dayjs, { Dayjs } from 'dayjs'; +import { Duration } from "dayjs/plugin/duration.js"; import utc from 'dayjs/plugin/utc.js'; -import {Logger} from '@foxxmd/logging'; +import { Request } from "express"; +import { accessSync, constants, promises } from "fs"; import JSON5 from 'json5'; -import {TimeoutError, WebapiError} from "spotify-web-api-node/src/response-error.js"; -import {Schema} from 'ajv'; -import * as AjvNS from 'ajv'; -import Ajv from 'ajv'; +import pathUtil from "path"; +import { getErrorCause } from "pony-cause"; +import { TimeoutError, WebapiError } from "spotify-web-api-node/src/response-error.js"; +import { PlayObject } from "../core/Atomic.js"; import { asPlayerStateData, NO_DEVICE, @@ -19,14 +26,6 @@ import { RemoteIdentityParts, ScrobbleThresholdResult, } from "./common/infrastructure/Atomic.js"; -import {Request} from "express"; -import pathUtil from "path"; -import {getErrorCause} from "pony-cause"; -import backoffStrategies from '@kenyip/backoff-strategies'; -import {replaceResultTransformer, stripIndentTransformer, TemplateTag, trimResultTransformer} from 'common-tags'; -import {Duration} from "dayjs/plugin/duration.js"; -import { PlayObject } from "../core/Atomic.js"; -import address from "address"; //const { default: Ajv } = AjvNS; dayjs.extend(utc); diff --git a/src/backend/utils/MDNSUtils.ts b/src/backend/utils/MDNSUtils.ts index baa31314..a7d76e0e 100644 --- a/src/backend/utils/MDNSUtils.ts +++ b/src/backend/utils/MDNSUtils.ts @@ -1,10 +1,10 @@ -import {Logger} from "@foxxmd/logging"; +import { Browser, Service, ServiceType } from "@astronautlabs/mdns"; +import { Logger } from "@foxxmd/logging"; import AvahiBrowser from 'avahi-browse'; +import { MdnsDeviceInfo } from "../common/infrastructure/Atomic.js"; import { MaybeLogger } from "../common/logging.js"; import { sleep } from "../utils.js"; -import { MdnsDeviceInfo } from "../common/infrastructure/Atomic.js"; -import {Browser, Service, ServiceType} from "@astronautlabs/mdns"; -import {debounce, DebouncedFunction} from "./debounce.js"; +import { debounce, DebouncedFunction } from "./debounce.js"; export interface AvahiService { service_name: string diff --git a/src/backend/utils/StringUtils.ts b/src/backend/utils/StringUtils.ts index e10eed15..278f79dc 100644 --- a/src/backend/utils/StringUtils.ts +++ b/src/backend/utils/StringUtils.ts @@ -1,7 +1,7 @@ +import { strategies, stringSameness, StringSamenessResult } from "@foxxmd/string-sameness"; +import { PlayObject } from "../../core/Atomic.js"; import { DELIMITERS } from "../common/infrastructure/Atomic.js"; import { parseRegexSingleOrFail } from "../utils.js"; -import { PlayObject } from "../../core/Atomic.js"; -import {stringSameness, StringSamenessResult, strategies} from "@foxxmd/string-sameness"; const {levenStrategy, diceStrategy} = strategies; diff --git a/src/backend/utils/TimeUtils.ts b/src/backend/utils/TimeUtils.ts index 5857e4b3..6bfeae86 100644 --- a/src/backend/utils/TimeUtils.ts +++ b/src/backend/utils/TimeUtils.ts @@ -1,3 +1,4 @@ +import dayjs, { Dayjs } from "dayjs"; import { PlayObject, SCROBBLE_TS_SOC_END, @@ -10,16 +11,15 @@ import { TemporalAccuracy, TemporalPlayComparison, } from "../../core/Atomic.js"; +import { capitalize } from "../../core/StringUtils.js"; import { DEFAULT_SCROBBLE_DURATION_THRESHOLD, DEFAULT_SCROBBLE_PERCENT_THRESHOLD, lowGranularitySources, ScrobbleThresholdResult, } from "../common/infrastructure/Atomic.js"; -import { formatNumber } from "../utils.js"; import { ScrobbleThresholds } from "../common/infrastructure/config/source/index.js"; -import { capitalize } from "../../core/StringUtils.js"; -import dayjs, {Dayjs} from "dayjs"; +import { formatNumber } from "../utils.js"; export const temporalPlayComparisonSummary = (data: TemporalPlayComparison, existingPlay?: PlayObject, candidatePlay?: PlayObject) => { const parts: string[] = []; diff --git a/src/core/Atomic.ts b/src/core/Atomic.ts index 45d3c649..fbf48bd5 100644 --- a/src/core/Atomic.ts +++ b/src/core/Atomic.ts @@ -1,6 +1,6 @@ -import {Dayjs} from "dayjs"; -import {ListenProgress} from "../backend/sources/PlayerState/ListenProgress.js"; -import {LogDataPretty, LogLevel} from "@foxxmd/logging"; +import { LogDataPretty, LogLevel } from "@foxxmd/logging"; +import { Dayjs } from "dayjs"; +import { ListenProgress } from "../backend/sources/PlayerState/ListenProgress.js"; export interface SourceStatusData { status: string; -- 2.51.2 From abe6638f66b7f1bf8838315220fdf5f79902e397 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 28 Mar 2024 12:28:56 -0400 Subject: [PATCH 03/13] refactor: Use updated logger to reduce log file path logic complexity --- package-lock.json | 49 ++++++++++++++++++++++++++++++++--- package.json | 2 +- src/backend/common/logging.ts | 24 +++++------------ src/backend/ioc.ts | 2 -- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6196f9a9..466a1781 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.13", + "@foxxmd/logging": "^0.1.14", "@foxxmd/string-sameness": "^0.4.0", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", @@ -124,6 +124,47 @@ "npm": ">=9.1.0" } }, + "../foxxmd/logging": { + "name": "@foxxmd/logging", + "version": "0.1.14", + "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", @@ -1519,9 +1560,9 @@ } }, "node_modules/@foxxmd/logging": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@foxxmd/logging/-/logging-0.1.13.tgz", - "integrity": "sha512-BEw20UxCQxvKdw76uIEPq2fpvr9rquC8tjp8SVeD6pTy9M7AIVpHwbeRv8+Kk2pDgEoJogkIIJrFCbPIcqwa9Q==", + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@foxxmd/logging/-/logging-0.1.14.tgz", + "integrity": "sha512-wvVoKWPzq4dEZu8FD3QcwDaCSaXocJSFimcFKevjxSspezl7NwML0qpH70KW4ES/IW6j0vH3HMK8vZ0avklLYA==", "dependencies": { "pino": "^8.19.0", "pino-abstract-transport": "^1.1.0", diff --git a/package.json b/package.json index c40063d0..e0c72097 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.13", + "@foxxmd/logging": "^0.1.14", "@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 45ac8379..c9cd5d05 100644 --- a/src/backend/common/logging.ts +++ b/src/backend/common/logging.ts @@ -1,11 +1,4 @@ -import { - childLogger, - Logger as FoxLogger, - loggerAppRolling, - LogLevel, - LogOptions as FoxLogOptions, - parseLogOptions, -} from '@foxxmd/logging'; +import { childLogger, Logger, loggerAppRolling, LogOptions, parseLogOptions, } from '@foxxmd/logging'; import { buildDestinationJsonPrettyStream, buildDestinationStdout, buildLogger } from "@foxxmd/logging/factory"; import { PassThrough, Transform } from "node:stream"; import path from "path"; @@ -17,7 +10,7 @@ if (typeof process.env.CONFIG_DIR === 'string') { logPath = path.resolve(process.env.CONFIG_DIR, './logs'); } -export const initLogger = (): [FoxLogger, Transform] => { +export const initLogger = (): [Logger, Transform] => { const opts = parseLogOptions({file: false, console: 'debug'}) const stream = new PassThrough({objectMode: true}); const logger = buildLogger('debug', [ @@ -27,17 +20,12 @@ export const initLogger = (): [FoxLogger, Transform] => { return [logger, stream]; } -export const appLogger = async (config: FoxLogOptions = {}): Promise<[FoxLogger, PassThrough]> => { +export const appLogger = async (config: LogOptions = {}): Promise<[Logger, 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, + logDefaultPath: './logs/scrobble.log', destinations: [ buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: true}) ] @@ -45,9 +33,9 @@ export const appLogger = async (config: FoxLogOptions = {}): Promise<[FoxLogger, return [logger, stream]; } export class MaybeLogger { - logger?: FoxLogger + logger?: Logger - constructor(logger?: FoxLogger, label?: string) { + constructor(logger?: Logger, label?: string) { if (logger !== undefined && label !== undefined) { this.logger = childLogger(logger, label); } else { diff --git a/src/backend/ioc.ts b/src/backend/ioc.ts index cfa7899d..dcaef66e 100644 --- a/src/backend/ioc.ts +++ b/src/backend/ioc.ts @@ -5,7 +5,6 @@ import { createContainer } from "iti"; import normalizeUrl from 'normalize-url'; import path from "path"; import { projectDir } from "./common/index.js"; -import { logPath } from "./common/logging.js"; import { WildcardEmitter } from "./common/WildcardEmitter.js"; import { Notifiers } from "./notifier/Notifiers.js"; import ScrobbleClients from "./scrobblers/ScrobbleClients.js"; @@ -59,7 +58,6 @@ const createRoot = (options?: RootOptions) => { return createContainer().add({ version, configDir: configDir, - logDir: logPath, isProd: process.env.NODE_ENV !== undefined && (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'prod'), port: process.env.PORT ?? port, clientEmitter: () => new WildcardEmitter(), -- 2.51.2 From 7a3e4e56e216e06c3cd95afdf9763991448591ba Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Tue, 2 Apr 2024 10:15:41 -0400 Subject: [PATCH 04/13] chore: Remove some unused packages --- package-lock.json | 7 ------- package.json | 2 -- 2 files changed, 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 466a1781..6bf00c94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,6 @@ "react-dom": "^18.2.0", "react-redux": "^8.1.2", "react-router-dom": "^6.15.0", - "safe-stable-stringify": "^1.1.1", "sass": "^1.70.0", "spotify-web-api-node": "^5.0.2", "superagent": "^8.0.9", @@ -73,7 +72,6 @@ "youtube-music-ts-api": "^1.7.0" }, "devDependencies": { - "@dbus-types/dbus": "^0.0.4", "@dbus-types/notifications": "^0.0.5", "@eslint/js": "^8.56.0", "@faker-js/faker": "^8.1.0", @@ -9587,11 +9585,6 @@ } ] }, - "node_modules/safe-stable-stringify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", - "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==" - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", diff --git a/package.json b/package.json index e0c72097..8199100c 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,6 @@ "react-dom": "^18.2.0", "react-redux": "^8.1.2", "react-router-dom": "^6.15.0", - "safe-stable-stringify": "^1.1.1", "sass": "^1.70.0", "spotify-web-api-node": "^5.0.2", "superagent": "^8.0.9", @@ -107,7 +106,6 @@ "youtube-music-ts-api": "^1.7.0" }, "devDependencies": { - "@dbus-types/dbus": "^0.0.4", "@dbus-types/notifications": "^0.0.5", "@eslint/js": "^8.56.0", "@faker-js/faker": "^8.1.0", -- 2.51.2 From 52b08efd8800aed57d391624b3a2b314b4173a81 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 4 Apr 2024 09:30:33 -0400 Subject: [PATCH 05/13] feat: Implement option for disabling web server #150 If a user does not need the dashboard or has security concerns about ingress and cannot control their hosting environment this enables reducing attack surface. Closes #150 --- src/backend/common/infrastructure/config/aioConfig.ts | 7 +++++++ src/backend/ioc.ts | 7 +++++++ src/backend/server/index.ts | 5 +++++ 3 files changed, 19 insertions(+) diff --git a/src/backend/common/infrastructure/config/aioConfig.ts b/src/backend/common/infrastructure/config/aioConfig.ts index 7546983a..03a8b1ef 100644 --- a/src/backend/common/infrastructure/config/aioConfig.ts +++ b/src/backend/common/infrastructure/config/aioConfig.ts @@ -45,6 +45,13 @@ export interface AIOConfig { logging?: LogOptions + /** + * Disable web server from running/listening on port. + * + * This will also make any ingress sources (Plex, Jellyfin, Tautulli, etc...) unusable + * */ + disableWeb?: boolean + /** * Enables ALL relevant logging and debug options for all sources/clients, when none are defined. * diff --git a/src/backend/ioc.ts b/src/backend/ioc.ts index dcaef66e..fffc04f6 100644 --- a/src/backend/ioc.ts +++ b/src/backend/ioc.ts @@ -47,19 +47,26 @@ export interface RootOptions { baseUrl?: string, port?: string | number logger: Logger + disableWeb?: boolean } const createRoot = (options?: RootOptions) => { const { port = 9078, baseUrl = process.env.BASE_URL, + disableWeb: dw } = options || {}; const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`); + let disableWeb = dw; + if(disableWeb === undefined) { + disableWeb = process.env.DISABLE_WEB === 'true'; + } 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, + disableWeb, clientEmitter: () => new WildcardEmitter(), sourceEmitter: () => new WildcardEmitter(), notifierEmitter: () => new EventEmitter(), diff --git a/src/backend/server/index.ts b/src/backend/server/index.ts index 56dcdb93..9779ade1 100644 --- a/src/backend/server/index.ts +++ b/src/backend/server/index.ts @@ -35,6 +35,11 @@ export const initServer = async (parentLogger: Logger, appLoggerStream: PassThro const root = getRoot(); + if(root.get('disableWeb')) { + logger.warn('API and Dashboard have been DISABLED. Note that any ingress sources (Plex, Jellyfin, Tautulli, etc...) will be unusable'); + return; + } + const isProd = root.get('isProd'); const port = root.get('port'); const local = root.get('localUrl'); -- 2.51.2 From 0af1496a0f05ea953c861fbf9ced0c387c749f8e Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 4 Apr 2024 09:30:42 -0400 Subject: [PATCH 06/13] chore: Update proxies --- src/backend/common/schema/aio-source.json | 26 ++-- src/backend/common/schema/aio.json | 143 +++++++++++++++++----- src/backend/common/schema/source.json | 26 ++-- 3 files changed, 135 insertions(+), 60 deletions(-) diff --git a/src/backend/common/schema/aio-source.json b/src/backend/common/schema/aio-source.json index 4e9595fa..98a767f2 100644 --- a/src/backend/common/schema/aio-source.json +++ b/src/backend/common/schema/aio-source.json @@ -1669,49 +1669,49 @@ "SourceAIOConfig": { "anyOf": [ { - "$ref": "#/definitions/SpotifySourceAIOConfig" + "$ref": "#/definitions/ChromecastSourceAIOConfig" }, { - "$ref": "#/definitions/PlexSourceAIOConfig" + "$ref": "#/definitions/DeezerSourceAIOConfig" }, { - "$ref": "#/definitions/TautulliSourceAIOConfig" + "$ref": "#/definitions/JellySourceAIOConfig" }, { - "$ref": "#/definitions/DeezerSourceAIOConfig" + "$ref": "#/definitions/JRiverSourceAIOConfig" }, { - "$ref": "#/definitions/SubsonicSourceAIOConfig" + "$ref": "#/definitions/KodiSourceAIOConfig" }, { - "$ref": "#/definitions/JellySourceAIOConfig" + "$ref": "#/definitions/LastFmSouceAIOConfig" }, { - "$ref": "#/definitions/LastFmSouceAIOConfig" + "$ref": "#/definitions/ListenBrainzSourceAIOConfig" }, { - "$ref": "#/definitions/YTMusicSourceAIOConfig" + "$ref": "#/definitions/MopidySourceAIOConfig" }, { "$ref": "#/definitions/MPRISSourceAIOConfig" }, { - "$ref": "#/definitions/MopidySourceAIOConfig" + "$ref": "#/definitions/PlexSourceAIOConfig" }, { - "$ref": "#/definitions/ListenBrainzSourceAIOConfig" + "$ref": "#/definitions/SpotifySourceAIOConfig" }, { - "$ref": "#/definitions/JRiverSourceAIOConfig" + "$ref": "#/definitions/SubsonicSourceAIOConfig" }, { - "$ref": "#/definitions/KodiSourceAIOConfig" + "$ref": "#/definitions/TautulliSourceAIOConfig" }, { "$ref": "#/definitions/WebScrobblerSourceAIOConfig" }, { - "$ref": "#/definitions/ChromecastSourceAIOConfig" + "$ref": "#/definitions/YTMusicSourceAIOConfig" } ], "title": "SourceAIOConfig" diff --git a/src/backend/common/schema/aio.json b/src/backend/common/schema/aio.json index 3a57e103..dedb8251 100644 --- a/src/backend/common/schema/aio.json +++ b/src/backend/common/schema/aio.json @@ -512,6 +512,73 @@ "title": "DeezerSourceAIOConfig", "type": "object" }, + "FileLogOptions": { + "properties": { + "frequency": { + "anyOf": [ + { + "enum": [ + "daily", + "hourly" + ], + "type": "string" + }, + { + "type": "number" + } + ], + "default": "'daily'", + "description": "The amount of time a given rolling log file is used. Can be combined with size.\n\nUse `daily` or `hourly` to rotate file every day (or every hour). Existing file within the current day (or hour) will be re-used.\n\nNumerical values will be considered as a number of milliseconds. Using a numerical value will always create a new file upon startup.", + "title": "frequency" + }, + "level": { + "description": "Specify the minimum log level to output to rotating files. If `false` no log files will be created.", + "enum": [ + "debug", + "error", + false, + "fatal", + "info", + "log", + "silent", + "verbose", + "warn" + ], + "title": "level" + }, + "path": { + "default": "'./logs/app.log'", + "description": "The path and filename to use for log files.\n\nIf using rolling files the filename will be appended with `.N` (a number) BEFORE the extension based on rolling status.\n\nMay also be specified using env LOG_PATH or a function that returns a string.\n\nIf path is relative the absolute path will be derived from `logBaseDir` (in `LoggerAppExtras`) which defaults to CWD", + "title": "path", + "type": [ + "string", + "object" + ] + }, + "size": { + "default": "'10MB'", + "description": "The maximum size of a given rolling log file.\n\nCan be combined with frequency. Use k, m and g to express values in KB, MB or GB.\n\nNumerical values will be considered as MB.", + "title": "size", + "type": [ + "string", + "number" + ] + }, + "timestamp": { + "default": "'auto'", + "description": "For rolling log files\n\nWhen\n* value passed to rolling destination is a string (`path` from LogOptions is a string) and\n* `frequency` is defined\n\nThis determines the format of the datetime inserted into the log file name:\n\n* `unix` - unix epoch timestamp in milliseconds\n* `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z'\n* `auto`\n * When frequency is `daily` only inserts date IE YYYY-MM-DD\n * Otherwise inserts full ISO8601 datetime", + "enum": [ + "auto", + "iso", + "unix" + ], + "title": "timestamp", + "type": "string" + } + }, + "title": "FileLogOptions", + "type": "object" + }, "GotifyConfig": { "properties": { "name": { @@ -1570,10 +1637,14 @@ "type": "object" }, "LogLevel": { + "description": "Names of log levels that can be invoked on the logger\n\nFrom lowest to highest:\n\n* `debug`\n* `verbose`\n* `log`\n* `info`\n* `warn`\n* `error`\n* `fatal`\n* `silent` (will never output anything)\n\nWhen used in `LogOptions` specifies the **minimum** level the output should log at.", "enum": [ "debug", "error", + "fatal", "info", + "log", + "silent", "verbose", "warn" ], @@ -1581,29 +1652,33 @@ "type": "string" }, "LogOptions": { + "description": "Configure log levels and file options for an AppLogger.\n\n```ts\nconst infoLogger = loggerApp({\n level: 'info' // console and file will log any levels `info` and above\n});\n\nconst logger = loggerApp({\n console: 'debug', // console will log `debug` and higher\n file: 'warn' // file will log `warn` and higher\n});\n\nconst fileLogger = loggerRollingApp({\n console: 'debug', // console will log `debug` and higher\n file: {\n level: 'warn', // file will log `warn` and higher\n path: '/my/cool/path/output.log', // optionally, output to log file at this path\n frequency: 'hourly', // optionally, rotate hourly\n }\n});\n```", "properties": { "console": { + "$ref": "#/definitions/LogLevel", "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" + "anyOf": [ + { + "$ref": "#/definitions/FileLogOptions" + }, + { + "enum": [ + "debug", + "error", + false, + "fatal", + "info", + "log", + "silent", + "verbose", + "warn" + ] + } ], + "description": "Specify the minimum log level to output to rotating files or file output options. If `false` no log files will be created.", "title": "file" }, "level": { @@ -1611,11 +1686,6 @@ "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": "LogOptions", @@ -2391,49 +2461,49 @@ "SourceAIOConfig": { "anyOf": [ { - "$ref": "#/definitions/SpotifySourceAIOConfig" + "$ref": "#/definitions/ChromecastSourceAIOConfig" }, { - "$ref": "#/definitions/PlexSourceAIOConfig" + "$ref": "#/definitions/DeezerSourceAIOConfig" }, { - "$ref": "#/definitions/TautulliSourceAIOConfig" + "$ref": "#/definitions/JellySourceAIOConfig" }, { - "$ref": "#/definitions/DeezerSourceAIOConfig" + "$ref": "#/definitions/JRiverSourceAIOConfig" }, { - "$ref": "#/definitions/SubsonicSourceAIOConfig" + "$ref": "#/definitions/KodiSourceAIOConfig" }, { - "$ref": "#/definitions/JellySourceAIOConfig" + "$ref": "#/definitions/LastFmSouceAIOConfig" }, { - "$ref": "#/definitions/LastFmSouceAIOConfig" + "$ref": "#/definitions/ListenBrainzSourceAIOConfig" }, { - "$ref": "#/definitions/YTMusicSourceAIOConfig" + "$ref": "#/definitions/MopidySourceAIOConfig" }, { "$ref": "#/definitions/MPRISSourceAIOConfig" }, { - "$ref": "#/definitions/MopidySourceAIOConfig" + "$ref": "#/definitions/PlexSourceAIOConfig" }, { - "$ref": "#/definitions/ListenBrainzSourceAIOConfig" + "$ref": "#/definitions/SpotifySourceAIOConfig" }, { - "$ref": "#/definitions/JRiverSourceAIOConfig" + "$ref": "#/definitions/SubsonicSourceAIOConfig" }, { - "$ref": "#/definitions/KodiSourceAIOConfig" + "$ref": "#/definitions/TautulliSourceAIOConfig" }, { "$ref": "#/definitions/WebScrobblerSourceAIOConfig" }, { - "$ref": "#/definitions/ChromecastSourceAIOConfig" + "$ref": "#/definitions/YTMusicSourceAIOConfig" } ], "title": "SourceAIOConfig" @@ -3166,6 +3236,11 @@ "title": "debugMode", "type": "boolean" }, + "disableWeb": { + "description": "Disable web server from running/listening on port.\n\nThis will also make any ingress sources (Plex, Jellyfin, Tautulli, etc...) unusable", + "title": "disableWeb", + "type": "boolean" + }, "logging": { "$ref": "#/definitions/LogOptions", "title": "logging" diff --git a/src/backend/common/schema/source.json b/src/backend/common/schema/source.json index 3f4fdbb5..86afd3bb 100644 --- a/src/backend/common/schema/source.json +++ b/src/backend/common/schema/source.json @@ -2,49 +2,49 @@ "$schema": "http://json-schema.org/draft-07/schema#", "anyOf": [ { - "$ref": "#/definitions/SpotifySourceConfig" + "$ref": "#/definitions/ChromecastSourceConfig" }, { - "$ref": "#/definitions/PlexSourceConfig" + "$ref": "#/definitions/DeezerSourceConfig" }, { - "$ref": "#/definitions/TautulliSourceConfig" + "$ref": "#/definitions/JellySourceConfig" }, { - "$ref": "#/definitions/DeezerSourceConfig" + "$ref": "#/definitions/JRiverSourceConfig" }, { - "$ref": "#/definitions/SubSonicSourceConfig" + "$ref": "#/definitions/KodiSourceConfig" }, { - "$ref": "#/definitions/JellySourceConfig" + "$ref": "#/definitions/LastfmSourceConfig" }, { - "$ref": "#/definitions/LastfmSourceConfig" + "$ref": "#/definitions/ListenBrainzSourceConfig" }, { - "$ref": "#/definitions/YTMusicSourceConfig" + "$ref": "#/definitions/MopidySourceConfig" }, { "$ref": "#/definitions/MPRISSourceConfig" }, { - "$ref": "#/definitions/MopidySourceConfig" + "$ref": "#/definitions/PlexSourceConfig" }, { - "$ref": "#/definitions/ListenBrainzSourceConfig" + "$ref": "#/definitions/SpotifySourceConfig" }, { - "$ref": "#/definitions/JRiverSourceConfig" + "$ref": "#/definitions/SubSonicSourceConfig" }, { - "$ref": "#/definitions/KodiSourceConfig" + "$ref": "#/definitions/TautulliSourceConfig" }, { "$ref": "#/definitions/WebScrobblerSourceConfig" }, { - "$ref": "#/definitions/ChromecastSourceConfig" + "$ref": "#/definitions/YTMusicSourceConfig" } ], "definitions": { -- 2.51.2 From 6b1ee836547a0e1b3613329c3aca804cb75d4ba5 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 4 Apr 2024 09:35:27 -0400 Subject: [PATCH 07/13] docs: Add config section and config example for disabling web server #150 --- config/config.json.example | 1 + docsite/docs/configuration/configuration.md | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/config/config.json.example b/config/config.json.example index bc12c14d..3497e774 100644 --- a/config/config.json.example +++ b/config/config.json.example @@ -1,5 +1,6 @@ { "debugMode": false, + "disableWeb": false, "sourceDefaults": { "maxPollRetries": 1, "maxRequestRetries": 1, diff --git a/docsite/docs/configuration/configuration.md b/docsite/docs/configuration/configuration.md index 3b2f62f9..fd882201 100644 --- a/docsite/docs/configuration/configuration.md +++ b/docsite/docs/configuration/configuration.md @@ -195,6 +195,15 @@ EX: Lastfm Redirect Url is `BASE_URL:PORT/lastfm/callback` -- Set `BASE_URL=http Useful when running with [docker](../installation/installation.md#docker) so that you do not need to specify redirect URLs for each configuration. +#### Disable Web + +If you do not need the dashboard and/or ingress sources, or have security concerns about ingress and cannot control their hosting environment, the web server and API can be disabled. Note that any **ingress-based sources will be unusable** (Plex, Jellyfin, Tautulli, etc...) if this is disabled. + +Disable using either: + +* ENV `DISABLE_WEB=true` +* In [All-in-One File](#all-in-one-file-configuration) use the top-level property `"disableWeb": true` + # Source Configurations ## [Spotify](https://www.spotify.com) -- 2.51.2 From 5967e3df4663a6bc4f2332debf19d6ebdf0b706b Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Fri, 5 Apr 2024 12:07:41 -0400 Subject: [PATCH 08/13] feat: Implement notifications via Apprise --- README.md | 2 +- docsite/docs/configuration/configuration.md | 19 +++ docsite/src/pages/index.mdx | 2 +- .../infrastructure/config/health/webhooks.ts | 34 +++- src/backend/common/schema/aio.json | 86 ++++++++++ .../notifier/AbstractWebhookNotifier.ts | 11 +- .../notifier/AppriseWebhookNotifier.ts | 155 ++++++++++++++++++ src/backend/notifier/Notifiers.ts | 7 +- 8 files changed, 307 insertions(+), 9 deletions(-) create mode 100644 src/backend/notifier/AppriseWebhookNotifier.ts diff --git a/README.md b/README.md index 718a841c..6a16e4ba 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ A javascript app to scrobble music you listened to, to [Maloja](https://github.c * [Maloja](/docsite/docs/configuration/configuration.md#maloja) * [Last.fm](/docsite/docs/configuration/configuration.md#lastfm) * [ListenBrainz](/docsite/docs/configuration/configuration.md#listenbrainz) -* Monitor status of Sources and Clients using [webhooks (Gotify or Ntfy)](/docsite/docs/configuration/configuration.md#webhook-configurations) or [healthcheck endpoint](/docsite/docs/configuration/configuration.md#health-endpoint) +* Monitor status of Sources and Clients using [webhooks (Gotify, Ntfy, Apprise)](/docsite/docs/configuration/configuration.md#webhook-configurations) or [healthcheck endpoint](/docsite/docs/configuration/configuration.md#health-endpoint) * Supports configuring for single or multiple users (scrobbling for your friends and family!) * Web server interface for stats, basic control, and detailed logs * Graceful network and client failure handling (queued scrobbles that auto-retry) diff --git a/docsite/docs/configuration/configuration.md b/docsite/docs/configuration/configuration.md index fd882201..73903bcb 100644 --- a/docsite/docs/configuration/configuration.md +++ b/docsite/docs/configuration/configuration.md @@ -983,6 +983,25 @@ EX } ``` +### [Apprise](https://github.com/caronc/apprise-api) + +Refer to the [config schema for AppriseConfig](https://json-schema.app/view/%23/%23%2Fdefinitions%2FAppriseConfig?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Fmulti-scrobbler%2Fmaster%2Fsrc%2Fbackend%2Fcommon%2Fschema%2Faio.json) + +multi-scrobbler supports [stateless](https://github.com/caronc/apprise-api?tab=readme-ov-file#stateless-solution) and [persistent storage](https://github.com/caronc/apprise-api?tab=readme-ov-file#persistent-storage-solution) endpoints as well as [tags](https://github.com/caronc/apprise-api?tab=readme-ov-file#tagging)/ + +EX + +```json5 +{ + "type": "apprise", + "name": "MyAppriseFriendlyNameForLogs", + "host": "http://192.168.0.100:8080", + "urls": ["gotify://192.168.0.101:8070/MyToken"], // stateless endpoints + "keys": ["e90b20526808373353afad7fb98a201198c0c3e0555bea19f182df3388af7b17"], //persistent storage endpoints + "tags": ["my","optional","tags"] +} +``` + ## Health Endpoint An endpoint for monitoring the health of sources/clients is available at GET `http://YourMultiScrobblerDomain/health` diff --git a/docsite/src/pages/index.mdx b/docsite/src/pages/index.mdx index f8ac6e18..982d95ef 100644 --- a/docsite/src/pages/index.mdx +++ b/docsite/src/pages/index.mdx @@ -31,7 +31,7 @@ A javascript app to scrobble music you listened to, to [Maloja](https://github.c * [Maloja](docs/configuration#maloja) * [Last.fm](docs/configuration#lastfm) * [ListenBrainz](docs/configuration#listenbrainz) -* Monitor status of Sources and Clients using [webhooks (Gotify or Ntfy)](docs/configuration#webhook-configurations) or [healthcheck endpoint](docs/configuration#health-endpoint) +* Monitor status of Sources and Clients using [webhooks (Gotify, Ntfy, Apprise)](docs/configuration#webhook-configurations) or [healthcheck endpoint](docs/configuration#health-endpoint) * Supports configuring for single or multiple users (scrobbling for your friends and family!) * Web server interface for stats, basic control, and detailed logs * Graceful network and client failure handling (queued scrobbles that auto-retry) diff --git a/src/backend/common/infrastructure/config/health/webhooks.ts b/src/backend/common/infrastructure/config/health/webhooks.ts index 128b7432..932bddee 100644 --- a/src/backend/common/infrastructure/config/health/webhooks.ts +++ b/src/backend/common/infrastructure/config/health/webhooks.ts @@ -1,9 +1,11 @@ export interface WebhookPayload { title?: string message: string - priority: 'info' | 'warn' | 'error' + priority: Priority } +export type Priority = 'info' | 'warn' | 'error'; + export interface PrioritiesConfig { /** * @examples [5] @@ -28,7 +30,7 @@ export interface CommonWebhookConfig { * * @examples ["gotify"] * */ - type: 'gotify' | 'ntfy' + type: 'gotify' | 'ntfy' | 'apprise' /** * A friendly name used to identify webhook config in logs * */ @@ -90,4 +92,30 @@ export interface NtfyConfig extends CommonWebhookConfig { priorities?: PrioritiesConfig } -export type WebhookConfig = GotifyConfig | NtfyConfig; +export interface AppriseConfig extends CommonWebhookConfig { + /** + * The URL of the apprise-api server + * + * @examples ["http://192.168.0.100:8078"] + * */ + host: string + + /** + * If using [Stateless Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#stateless-solution) the Apprise config URL(s) to send + * */ + urls?: string | string[] + + /** + * If using [Persistent Store Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#persistent-storage-solution) the Configuration ID(s) to send to + * + * Note: If multiple keys are defined then MS will attempt to POST to each one individually + * */ + keys?: string | string[] + + /** + * Optional [tag(s)](https://github.com/caronc/apprise-api?tab=readme-ov-file#tagging) to send in the notification payload + * */ + tags?: string | string[] +} + +export type WebhookConfig = GotifyConfig | NtfyConfig | AppriseConfig; diff --git a/src/backend/common/schema/aio.json b/src/backend/common/schema/aio.json index dedb8251..7c2e180f 100644 --- a/src/backend/common/schema/aio.json +++ b/src/backend/common/schema/aio.json @@ -1,6 +1,87 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { + "AppriseConfig": { + "properties": { + "host": { + "description": "The URL of the apprise-api server", + "examples": [ + "http://192.168.0.100:8078" + ], + "title": "host", + "type": "string" + }, + "keys": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "If using [Persistent Store Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#persistent-storage-solution) the Configuration ID(s) to send to\n\nNote: If multiple keys are defined then MS will attempt to POST to each one individually", + "title": "keys" + }, + "name": { + "description": "A friendly name used to identify webhook config in logs", + "title": "name", + "type": "string" + }, + "tags": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "Optional [tag(s)](https://github.com/caronc/apprise-api?tab=readme-ov-file#tagging) to send in the notification payload", + "title": "tags" + }, + "type": { + "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", + "enum": [ + "apprise", + "gotify", + "ntfy" + ], + "examples": [ + "gotify" + ], + "title": "type", + "type": "string" + }, + "urls": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "description": "If using [Stateless Endpoints](https://github.com/caronc/apprise-api?tab=readme-ov-file#stateless-solution) the Apprise config URL(s) to send", + "title": "urls" + } + }, + "required": [ + "host", + "type" + ], + "title": "AppriseConfig", + "type": "object" + }, "ChromecastData": { "properties": { "allowUnknownMedia": { @@ -602,6 +683,7 @@ "type": { "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", "enum": [ + "apprise", "gotify", "ntfy" ], @@ -2170,6 +2252,7 @@ "type": { "description": "Webhook type. Valid values are:\n\n* gotify\n* ntfy", "enum": [ + "apprise", "gotify", "ntfy" ], @@ -3070,6 +3153,9 @@ }, { "$ref": "#/definitions/NtfyConfig" + }, + { + "$ref": "#/definitions/AppriseConfig" } ], "title": "WebhookConfig" diff --git a/src/backend/notifier/AbstractWebhookNotifier.ts b/src/backend/notifier/AbstractWebhookNotifier.ts index 4181a09b..089b1cdb 100644 --- a/src/backend/notifier/AbstractWebhookNotifier.ts +++ b/src/backend/notifier/AbstractWebhookNotifier.ts @@ -1,16 +1,21 @@ import { childLogger, Logger } from "@foxxmd/logging"; -import { GotifyConfig, NtfyConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; +import { + AppriseConfig, + GotifyConfig, + NtfyConfig, + WebhookPayload +} from "../common/infrastructure/config/health/webhooks.js"; export abstract class AbstractWebhookNotifier { - config: GotifyConfig | NtfyConfig + config: GotifyConfig | NtfyConfig | AppriseConfig logger: Logger; initialized: boolean = false; requiresAuth: boolean = false; authed: boolean = false; - protected constructor(type: string, defaultName: string, config: GotifyConfig | NtfyConfig, logger: Logger) { + protected constructor(type: string, defaultName: string, config: GotifyConfig | NtfyConfig | AppriseConfig, logger: Logger) { this.config = config; const label = `${type} - ${config.name ?? defaultName}` this.logger = childLogger(logger, label); diff --git a/src/backend/notifier/AppriseWebhookNotifier.ts b/src/backend/notifier/AppriseWebhookNotifier.ts new file mode 100644 index 00000000..6697b659 --- /dev/null +++ b/src/backend/notifier/AppriseWebhookNotifier.ts @@ -0,0 +1,155 @@ +import { Logger } from "@foxxmd/logging"; +import request, { Request } from "superagent"; +import { truncateStringToLength } from "../../core/StringUtils.js"; +import { isSuperAgentResponseError } from "../common/errors/ErrorUtils.js"; +import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; +import { UpstreamError } from "../common/errors/UpstreamError.js"; +import { + AppriseConfig, + PrioritiesConfig, + Priority, + WebhookPayload +} from "../common/infrastructure/config/health/webhooks.js"; +import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; + +const shortKey = truncateStringToLength(10); + +export class AppriseWebhookNotifier extends AbstractWebhookNotifier { + + declare config: AppriseConfig; + + priorities: PrioritiesConfig; + + urls: string[]; + keys: string[]; + + constructor(defaultName: string, config: AppriseConfig, logger: Logger) { + super('Apprise', defaultName, config, logger); + const { + urls = [], + keys = [], + host, + } = this.config; + if (host === undefined) { + throw new Error(`'host' must be defined in configuration for this notification`); + } + this.urls = Array.isArray(urls) ? urls : [urls]; + this.keys = Array.isArray(keys) ? keys : [keys]; + + if (this.urls.length === 0 && this.keys.length === 0) { + this.logger.warn(`No 'urls' or 'keys' were defined! Will assume stateless (POST ${host}/notify) and that you have the ENV 'APPRISE_STATELESS_URLS' set on your Apprise instance`); + } + } + + initialize = async () => { + // check url is correct + try { + await request.get(this.config.host); + } catch (e) { + this.logger.error(new Error('Failed to contact Apprise server', {cause: e})); + } + + if (this.keys.length > 0) { + let anyOk = false; + for (const key of this.keys) { + try { + const resp = await request.get(`${this.config.host}/json/urls/${key}`); + if (resp.statusCode === 204) { + this.logger.warn(`Details for Config ${shortKey(key)} returned no content. Double check the key is set correctly or that the apprise Config is not empty.`); + } else { + anyOk = true; + } + } catch (e) { + this.logger.warn(new Error(`Failed to get details for Config ${shortKey(key)}`, {cause: e})); + } + } + if (!anyOk) { + this.logger.error('No Apprise Configs were valid!'); + this.initialized = false; + return; + } + } + this.initialized = true; + } + + doNotify = async (payload: WebhookPayload) => { + const body: Record = { + title: payload.title, + body: payload.message, + type: convertPriorityToType(payload.priority) + } + + let anyOk = false; + if (this.keys.length > 0) { + for (const key of this.keys) { + try { + const resp = await this.callApi(request.post(`${this.config.host}/notify/${key}`) + .type('json') + .send(body)); + anyOk = true; + this.logger.debug(`Pushed notification to Config ${shortKey(key)}`); + } catch (e: any) { + this.logger.warn(new Error(`Failed to push notification for '${payload.title}' to Config ${shortKey(key)}`, {cause: e})); + } + } + } + + if (this.urls.length > 0 || this.keys.length === 0) { + if (this.urls.length > 0) { + body.urls = this.urls.join(',') + } + try { + const resp = await this.callApi(request.post(`${this.config.host}/notify`) + .type('json') + .send(body)); + anyOk = true; + this.logger.debug(`Pushed notification to URLs`); + } catch (e: any) { + this.logger.warn(`Failed to push notification for '${payload.title}' to URLs`, {cause: e}); + } + } + + if (!anyOk) { + this.logger.error(`Failed to push any notifications!`) + } + } + + callApi = async (req: Request, retries = 0): Promise => { + try { + return await req as T; + } catch (e) { + if (isNodeNetworkException(e) || isSuperAgentResponseError(e) && e.timeout) { + throw new UpstreamError('Request failed to due a network issue', {cause: e}); + } else if (isSuperAgentResponseError(e)) { + const { + message, + status, + response: { + body: jsonBody = undefined, + text = undefined, + } = {} + } = e; + const errorMsgs = [message]; + if (typeof jsonBody === 'object' && jsonBody.error !== undefined) { + errorMsgs.push(jsonBody.error); + } + throw new UpstreamError(`Apprise API Request failed => (${status}) ${errorMsgs.join(' => ')}`, {response: e.response}); + } else { + throw new Error('Non API Request error encountered', {cause: e}); + } + } + } +} + +const convertPriorityToType = (priority?: Priority): 'info' | 'success' | 'warning' | 'failure' => { + switch (priority) { + case 'info': + return 'info'; + case 'warn': + return 'warning'; + case 'error': + return 'failure'; + default: + return 'info'; + } +} diff --git a/src/backend/notifier/Notifiers.ts b/src/backend/notifier/Notifiers.ts index 8fae13b2..7af71e16 100644 --- a/src/backend/notifier/Notifiers.ts +++ b/src/backend/notifier/Notifiers.ts @@ -1,12 +1,14 @@ import { childLogger, Logger } from '@foxxmd/logging'; import { EventEmitter } from "events"; import { + AppriseConfig, GotifyConfig, NtfyConfig, WebhookConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; +import { AppriseWebhookNotifier } from "./AppriseWebhookNotifier.js"; import { GotifyWebhookNotifier } from "./GotifyWebhookNotifier.js"; import { NtfyWebhookNotifier } from "./NtfyWebhookNotifier.js"; @@ -26,7 +28,7 @@ export class Notifiers { this.clientEmitter = clientEmitter; this.sourceEmitter = sourceEmitter; - this.logger = childLogger(parentLogger, 'Notifiers'); // winston.loggers.get('app').child({labels: ['Notifiers']}, mergeArr); + this.logger = childLogger(parentLogger, 'Notifiers'); this.sourceEmitter.on('notify', async (payload: WebhookPayload) => { await this.notify(payload); @@ -44,6 +46,9 @@ export class Notifiers { case 'ntfy': webhook = new NtfyWebhookNotifier(defaultName, config as NtfyConfig, this.logger); break; + case 'apprise': + webhook = new AppriseWebhookNotifier(defaultName, config as AppriseConfig, this.logger); + break; default: this.logger.error(`'${config.type}' is not a valid webhook type`); continue; -- 2.51.2 From 0572d6a3081dad0b80e57470bdbca8d29cf3ff02 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 10 Apr 2024 10:19:58 -0400 Subject: [PATCH 09/13] refactor(logging): Reduce noise for polling interval logging --- src/backend/sources/AbstractSource.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index ed03b8f2..ce5a3173 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -517,20 +517,18 @@ export default abstract class AbstractSource implements Authenticatable { const activeThreshold = this.lastActivityAt.add(checkActiveFor, 's'); const inactiveFor = dayjs.duration(Math.abs(activeThreshold.diff(dayjs(), 'millisecond'))).humanize(false); if (activeThreshold.isBefore(dayjs())) { + let intervalStr: string = formatNumber(maxInterval); checksOverThreshold++; if(sleepTime < maxInterval) { const checkVal = Math.min(checksOverThreshold, 1000); const backoff = Math.round(Math.max(Math.min(Math.min(checkVal, 1000) * 2 * (1.1 * checkVal), maxBackoff), 5)); + intervalStr = `(${interval} + ${backoff})`; sleepTime = interval + backoff; - this.logger.debug(`Last activity was at ${this.lastActivityAt.format()} which is ${inactiveFor} outside of active polling period of (last activity + ${checkActiveFor} seconds). Will check again in interval ${interval} + ${backoff} seconds.`); - } else { - this.logger.debug(`Last activity was at ${this.lastActivityAt.format()} which is ${inactiveFor} outside of active polling period of (last activity + ${checkActiveFor} seconds). Will check again in max interval ${maxInterval} seconds.`); } + this.logger.debug(`Last activity ${this.lastActivityAt.format()} is ${inactiveFor} outside of polling period (last activity + ${checkActiveFor}s) | Next check interval: ${intervalStr}s`); } else { - this.logger.debug(`Last activity was at ${this.lastActivityAt.format()}. Will check again in interval ${formatNumber(sleepTime)} seconds.`); + this.logger.debug(`Last activity was at ${this.lastActivityAt.format()} | Next check interval: ${formatNumber(sleepTime)}s`); } - - this.logger.verbose(`Sleeping for ${formatNumber(sleepTime)}s`); const wakeUpAt = pollFrom.add(sleepTime, 'seconds'); while(!this.shouldStopPolling() && dayjs().isBefore(wakeUpAt)) { // check for polling status every half second and wait till wake up time -- 2.51.2 From cede77e3fa240b86d2fb70dc0f6baf08f304d5d0 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 10 Apr 2024 10:56:04 -0400 Subject: [PATCH 10/13] refactor: Remove pony-cause dependency Use built-in error cause and adapt helper functions from pony-cause so we can remove a dependency --- package-lock.json | 9 -- package.json | 1 - src/backend/common/errors/NodeErrors.ts | 2 +- src/backend/common/errors/UpstreamError.ts | 3 +- .../common/vendor/maloja/interfaces.ts | 3 +- .../scrobblers/AbstractScrobbleClient.ts | 2 +- src/backend/sources/AbstractSource.ts | 2 +- src/backend/sources/ChromecastSource.ts | 2 +- src/backend/sources/SpotifySource.ts | 2 +- src/backend/sources/SubsonicSource.ts | 3 +- src/backend/utils.ts | 28 ----- src/backend/utils/ErrorUtils.ts | 107 ++++++++++++++++++ 12 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 src/backend/utils/ErrorUtils.ts diff --git a/package-lock.json b/package-lock.json index 6bf00c94..685266a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,6 @@ "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", "react-dom": "^18.2.0", @@ -8749,14 +8748,6 @@ "node": ">=4" } }, - "node_modules/pony-cause": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-1.1.1.tgz", - "integrity": "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g==", - "engines": { - "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", diff --git a/package.json b/package.json index 8199100c..48ac9330 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,6 @@ "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", "react-dom": "^18.2.0", diff --git a/src/backend/common/errors/NodeErrors.ts b/src/backend/common/errors/NodeErrors.ts index adfe0e61..ae99639f 100644 --- a/src/backend/common/errors/NodeErrors.ts +++ b/src/backend/common/errors/NodeErrors.ts @@ -1,4 +1,4 @@ -import { findCauseByFunc } from "../../utils.js"; +import { findCauseByFunc } from "../../utils/ErrorUtils.js"; import { isArbitraryObject } from "../infrastructure/Atomic.js"; import ErrnoException = NodeJS.ErrnoException; diff --git a/src/backend/common/errors/UpstreamError.ts b/src/backend/common/errors/UpstreamError.ts index fc0ab357..7df9d061 100644 --- a/src/backend/common/errors/UpstreamError.ts +++ b/src/backend/common/errors/UpstreamError.ts @@ -1,5 +1,6 @@ import { Response } from 'superagent'; -import { findCauseByFunc } from "../../utils.js"; + +import { findCauseByFunc } from "../../utils/ErrorUtils.js"; export class UpstreamError extends Error { diff --git a/src/backend/common/vendor/maloja/interfaces.ts b/src/backend/common/vendor/maloja/interfaces.ts index a3ac3724..8efec702 100644 --- a/src/backend/common/vendor/maloja/interfaces.ts +++ b/src/backend/common/vendor/maloja/interfaces.ts @@ -1,5 +1,6 @@ import { ResponseError } from "superagent"; -import { findCauseByFunc } from "../../../utils.js"; + +import { findCauseByFunc } from "../../../utils/ErrorUtils.js"; import { isSuperAgentResponseError } from "../../errors/ErrorUtils.js"; export interface MalojaV2ScrobbleData { diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index d9d7f4df..f4e0e862 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -3,7 +3,6 @@ import dayjs, { Dayjs } from "dayjs"; import EventEmitter from "events"; import { FixedSizeList } from 'fixed-size-list'; import { nanoid } from "nanoid"; -import { messageWithCauses } from "pony-cause"; import { DeadLetterScrobble, PlayObject, @@ -40,6 +39,7 @@ import { sleep, sortByOldestPlayDate, } from "../utils.js"; +import { messageWithCauses } from "../utils/ErrorUtils.js"; import { compareScrobbleArtists, compareScrobbleTracks, normalizeStr } from "../utils/StringUtils.js"; import { comparePlayTemporally, diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index ce5a3173..a93c6351 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -23,7 +23,6 @@ import { import { SourceConfig } from "../common/infrastructure/config/source/sources.js"; import TupleMap from "../common/TupleMap.js"; import { - findCauseByFunc, formatNumber, genGroupId, playObjDataMatch, @@ -32,6 +31,7 @@ import { sortByNewestPlayDate, sortByOldestPlayDate, } from "../utils.js"; +import { findCauseByFunc } from "../utils/ErrorUtils.js"; import { comparePlayTemporally, temporalAccuracyIsAtLeast } from "../utils/TimeUtils.js"; export interface RecentlyPlayedOptions { diff --git a/src/backend/sources/ChromecastSource.ts b/src/backend/sources/ChromecastSource.ts index 1745ba3b..fcd6c9a3 100644 --- a/src/backend/sources/ChromecastSource.ts +++ b/src/backend/sources/ChromecastSource.ts @@ -5,7 +5,6 @@ import { Client as CastClient } from 'castv2'; import dayjs from "dayjs"; import { EventEmitter } from "events"; import e from "express"; -import { findCauseByReference } from "pony-cause"; import { PlayObject } from "../../core/Atomic.js"; import { buildTrackString } from "../../core/StringUtils.js"; import { NETWORK_ERROR_FAILURE_CODES } from "../common/errors/NodeErrors.js"; @@ -32,6 +31,7 @@ import { PlatformType } from "../common/vendor/chromecast/interfaces.js"; import { difference, genGroupIdStr, parseBool } from "../utils.js"; +import { findCauseByReference } from "../utils/ErrorUtils.js"; import { discoveryAvahi, discoveryNative } from "../utils/MDNSUtils.js"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; import MemorySource from "./MemorySource.js"; diff --git a/src/backend/sources/SpotifySource.ts b/src/backend/sources/SpotifySource.ts index aff68d14..905eeeff 100644 --- a/src/backend/sources/SpotifySource.ts +++ b/src/backend/sources/SpotifySource.ts @@ -19,13 +19,13 @@ import { import { SpotifySourceConfig } from "../common/infrastructure/config/source/spotify.js"; import { combinePartsToString, - findCauseByFunc, parseRetryAfterSecsFromObj, readJson, sleep, sortByOldestPlayDate, writeFile, } from "../utils.js"; +import { findCauseByFunc } from "../utils/ErrorUtils.js"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; import MemorySource from "./MemorySource.js"; import AlbumObjectSimplified = SpotifyApi.AlbumObjectSimplified; diff --git a/src/backend/sources/SubsonicSource.ts b/src/backend/sources/SubsonicSource.ts index 73c6ff58..ae8f3626 100644 --- a/src/backend/sources/SubsonicSource.ts +++ b/src/backend/sources/SubsonicSource.ts @@ -9,7 +9,8 @@ import { UpstreamError } from "../common/errors/UpstreamError.js"; import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; import { SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js"; import { getSubsonicResponse, SubsonicResponse, SubsonicResponseCommon } from "../common/vendor/subsonic/interfaces.js"; -import { findCauseByFunc, parseRetryAfterSecsFromObj, removeDuplicates, sleep } from "../utils.js"; +import { parseRetryAfterSecsFromObj, removeDuplicates, sleep } from "../utils.js"; +import { findCauseByFunc } from "../utils/ErrorUtils.js"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; import MemorySource from "./MemorySource.js"; diff --git a/src/backend/utils.ts b/src/backend/utils.ts index f3a280fc..9007e1a7 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -11,7 +11,6 @@ import { Request } from "express"; import { accessSync, constants, promises } from "fs"; import JSON5 from 'json5'; import pathUtil from "path"; -import { getErrorCause } from "pony-cause"; import { TimeoutError, WebapiError } from "spotify-web-api-node/src/response-error.js"; import { PlayObject } from "../core/Atomic.js"; import { @@ -766,30 +765,3 @@ export const comparingMultipleArtists = (existing: PlayObject, candidate: PlayOb return eArtists.length > 1 || cArtists.length > 1; } -/** - * Adapted from https://github.com/voxpelli/pony-cause/blob/main/lib/helpers.js to find cause by truthy function - * */ -export const findCauseByFunc = (err: any, func: (e: Error) => boolean) => { - if (!err || !func) return; - if (!(err instanceof Error)) return; - if (typeof func !== 'function') { - return; - } - - /** - * Ensures we don't go circular - */ - const seen = new Set(); - - let currentErr: Error | undefined = err; - - while (currentErr && !seen.has(currentErr)) { - seen.add(currentErr); - - if (func(currentErr)) { - return currentErr; - } - - currentErr = getErrorCause(currentErr); - } -}; diff --git a/src/backend/utils/ErrorUtils.ts b/src/backend/utils/ErrorUtils.ts new file mode 100644 index 00000000..726ebd90 --- /dev/null +++ b/src/backend/utils/ErrorUtils.ts @@ -0,0 +1,107 @@ +/** + * Adapted from https://github.com/voxpelli/pony-cause/blob/main/lib/helpers.js to find cause by truthy function + * */ +export const findCauseByFunc = (err: any, func: (e: Error) => boolean) => { + if (!err || !func) return; + if (!(err instanceof Error)) return; + if (typeof func !== 'function') { + return; + } + + /** + * Ensures we don't go circular + */ + const seen = new Set(); + + let currentErr: Error | undefined = err; + + while (currentErr && !seen.has(currentErr)) { + seen.add(currentErr); + + if (func(currentErr)) { + return currentErr; + } + + currentErr = getErrorCause(currentErr); + } +}; +export const findCauseByMessage = (err: any, msg: string) => { + return findCauseByFunc(err, (e => e.message.toLocaleLowerCase().includes(msg.toLocaleLowerCase()))); +} +/** + * Adapted from https://github.com/voxpelli/pony-cause + * */ +export const getErrorCause = (err: Error | { + cause?: unknown | (() => Error | { + cause?: unknown | (() => Error | any); + }); +}): Error | undefined => { + if (!err) return; + + const cause = err.cause; + + // VError / NError style causes + if (typeof cause === 'function') { + const causeResult = cause(); + + return causeResult instanceof Error + ? causeResult + : undefined; + } else { + return cause instanceof Error + ? cause + : undefined; + } +}; +/** + * Adapted from https://github.com/voxpelli/pony-cause + * */ +export const findCauseByReference = (err: unknown, reference: new (...args: any[]) => T): T | undefined => { + if (!err || !reference) return; + if (!(err instanceof Error)) return; + if (!(reference.prototype instanceof Error) && + // @ts-expect-error we are purposely checking if ref is generic error class + reference !== Error) return; + + const seen = new Set(); + + let currentErr = err; + + while (currentErr && !seen.has(currentErr)) { + seen.add(currentErr); + + if (currentErr instanceof reference) { + return currentErr; + } + + currentErr = getErrorCause(currentErr); + } +}; +/** + * Adapted from https://github.com/voxpelli/pony-cause + * */ +const _messageWithCauses = (err: Error, seen = new Set()) => { + if (!(err instanceof Error)) return ''; + + const message = err.message; + + // Ensure we don't go circular or crazily deep + if (seen.has(err)) { + return message + ': ...'; + } + + const cause = getErrorCause(err); + + if (cause) { + seen.add(err); + + return (message + ': ' + + _messageWithCauses(cause, seen)); + } else { + return message; + } +}; +/** + * Adapted from https://github.com/voxpelli/pony-cause + * */ +export const messageWithCauses = (err: Error) => _messageWithCauses(err); -- 2.51.2 From fb73b8b106e56c59cfcc9fb7f6c00bb4326d4358 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 10 Apr 2024 10:57:02 -0400 Subject: [PATCH 11/13] feat(mpris): Less noisy error handling * Convert DBus non-standard thrown error data to regular Errors * Only warn about position errors if error message is unexpected --- src/backend/sources/MPRISSource.ts | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/backend/sources/MPRISSource.ts b/src/backend/sources/MPRISSource.ts index 37647900..33cb1c00 100644 --- a/src/backend/sources/MPRISSource.ts +++ b/src/backend/sources/MPRISSource.ts @@ -14,6 +14,7 @@ import { PlayerInfo, } from "../common/infrastructure/config/source/mpris.js"; import { removeDuplicates } from "../utils.js"; +import { findCauseByMessage } from "../utils/ErrorUtils.js"; import { RecentlyPlayedOptions } from "./AbstractSource.js"; import MemorySource from "./MemorySource.js"; @@ -128,7 +129,10 @@ export class MPRISSource extends MemorySource { try { pos = await this.getPlayerPosition(props); } catch (e) { - this.logger.warn(`Could not get Position info for player ${plainPlayerName}`); + // only log if the error is not related to position not being supported since this is a potentially expected result + if(!findCauseByMessage(e, 'Position is not supported')) { + this.logger.warn(new Error(`Could not get Position info for player ${plainPlayerName}`, {cause: e})); + } } const status = await this.getPlayerStatus(props); if (status === PLAYBACK_STATUS_STOPPED && activeOnly) { @@ -141,9 +145,8 @@ export class MPRISSource extends MemorySource { position: pos, metadata }); - } - catch (e) { - this.logger.warn(new Error(`Could not parse D-bus info for player ${plainPlayerName}`, {cause: e})); + } catch (e) { + this.logger.warn(new Error(`Could not parse D-bus info for player ${plainPlayerName}`, {cause: convertDBusExceptionToError(e)})); } } @@ -157,7 +160,7 @@ export class MPRISSource extends MemorySource { // microseconds return dayjs.duration({milliseconds: Number(pos / 1000)}).asSeconds(); } catch(e) { - throw new Error('Could not get player Position', {cause: e}); + throw new Error('Could not get player Position', {cause: convertDBusExceptionToError(e)}); } } @@ -166,7 +169,7 @@ export class MPRISSource extends MemorySource { const status = await props['PlaybackStatus']; return status as PlaybackStatus; } catch (e) { - throw new Error('Could not get player PlaybackStatus', {cause: e}) + throw new Error('Could not get player PlaybackStatus', {cause: convertDBusExceptionToError(e)}) } } @@ -175,7 +178,7 @@ export class MPRISSource extends MemorySource { const metadata = await props['Metadata']; return this.metadataToPlain(metadata); } catch(e) { - throw new Error('Could not get player Metadata', {cause: e}); + throw new Error('Could not get player Metadata', {cause: convertDBusExceptionToError(e)}); } } @@ -224,4 +227,15 @@ export class MPRISSource extends MemorySource { } } +const convertDBusExceptionToError = (e: any): Error => { + let err: Error; + if(e instanceof Error) { + err = e; + } else if(Array.isArray(e)) { + err = new Error(e.map(x => x.toString()).join(' | ')); + } else { + err = new Error(e.toString()); + } + return err; +} -- 2.51.2 From 3d893b126f574020ddf12628a454ff72b3194d9d Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 10 Apr 2024 11:25:27 -0400 Subject: [PATCH 12/13] refactor(logging): Balance debug/verbose level logging pt.1 * Move some debug => verbose for non-repeating logs that are relevant to state, notifications, and scrobbling * Move some verbose => debug that were repeating and making verbose level noisy --- src/backend/index.ts | 2 +- src/backend/notifier/AppriseWebhookNotifier.ts | 4 ++-- src/backend/notifier/GotifyWebhookNotifier.ts | 6 +++--- src/backend/notifier/NtfyWebhookNotifier.ts | 4 ++-- src/backend/server/api.ts | 16 ++++++++-------- src/backend/sources/AbstractSource.ts | 4 ++-- src/backend/sources/ChromecastSource.ts | 2 +- src/backend/sources/JRiverSource.ts | 2 +- src/backend/sources/KodiSource.ts | 2 +- src/backend/sources/MemorySource.ts | 6 +++--- src/backend/sources/MopidySource.ts | 2 +- .../sources/PlayerState/AbstractPlayerState.ts | 4 ++-- src/backend/sources/ScrobbleSources.ts | 4 ++-- 13 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/backend/index.ts b/src/backend/index.ts index 524efa23..dea3e85c 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -50,7 +50,7 @@ process.on('uncaughtExceptionMonitor', (err, origin) => { const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`); try { - initLogger.debug(`Config Dir ENV: ${process.env.CONFIG_DIR} -> Resolved: ${configDir}`) + initLogger.verbose(`Config Dir ENV: ${process.env.CONFIG_DIR} -> Resolved: ${configDir}`) // try to read a configuration file let appConfigFail: Error | undefined = undefined; let config = {}; diff --git a/src/backend/notifier/AppriseWebhookNotifier.ts b/src/backend/notifier/AppriseWebhookNotifier.ts index 6697b659..72e4e459 100644 --- a/src/backend/notifier/AppriseWebhookNotifier.ts +++ b/src/backend/notifier/AppriseWebhookNotifier.ts @@ -87,7 +87,7 @@ export class AppriseWebhookNotifier extends AbstractWebhookNotifier { .type('json') .send(body)); anyOk = true; - this.logger.debug(`Pushed notification to Config ${shortKey(key)}`); + this.logger.verbose(`Pushed notification to Config ${shortKey(key)}`); } catch (e: any) { this.logger.warn(new Error(`Failed to push notification for '${payload.title}' to Config ${shortKey(key)}`, {cause: e})); } @@ -103,7 +103,7 @@ export class AppriseWebhookNotifier extends AbstractWebhookNotifier { .type('json') .send(body)); anyOk = true; - this.logger.debug(`Pushed notification to URLs`); + this.logger.verbose(`Pushed notification to URLs`); } catch (e: any) { this.logger.warn(`Failed to push notification for '${payload.title}' to URLs`, {cause: e}); } diff --git a/src/backend/notifier/GotifyWebhookNotifier.ts b/src/backend/notifier/GotifyWebhookNotifier.ts index fa6f93b6..b0bedfc6 100644 --- a/src/backend/notifier/GotifyWebhookNotifier.ts +++ b/src/backend/notifier/GotifyWebhookNotifier.ts @@ -54,13 +54,13 @@ export class GotifyWebhookNotifier extends AbstractWebhookNotifier { title: payload.title, priority: this.priorities[payload.priority] }); - this.logger.debug(`Pushed notification.`); + this.logger.verbose(`Pushed notification.`); } catch (e: any) { if(e instanceof HTTPError && e.response.statusCode === 401) { - this.logger.error(`Unable to push notification. Error returned with 401 which means the TOKEN provided is probably incorrect. Disabling Notifier | Error => ${e.response.body}`); + this.logger.warn(`Unable to push notification. Error returned with 401 which means the TOKEN provided is probably incorrect. Disabling Notifier | Error => ${e.response.body}`); this.authed = false; } else { - this.logger.error(`Failed to push notification | Error => ${e.message}`); + this.logger.warn(`Failed to push notification | Error => ${e.message}`); } } } diff --git a/src/backend/notifier/NtfyWebhookNotifier.ts b/src/backend/notifier/NtfyWebhookNotifier.ts index a5aeeae7..570ab1c4 100644 --- a/src/backend/notifier/NtfyWebhookNotifier.ts +++ b/src/backend/notifier/NtfyWebhookNotifier.ts @@ -65,9 +65,9 @@ export class NtfyWebhookNotifier extends AbstractWebhookNotifier { } } await publish(req); - this.logger.debug(`Pushed notification.`); + this.logger.verbose(`Pushed notification.`); } catch (e: any) { - this.logger.error(`Failed to push notification: ${e.message}`) + this.logger.warn(`Failed to push notification: ${e.message}`) } } diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index d7ca2e38..05d5a998 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -293,7 +293,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: scrobbleClient: client, } = req; - (client as AbstractScrobbleClient).logger.debug('User requested processing of all dead letter scrobbles via API'); + (client as AbstractScrobbleClient).logger.verbose('User requested processing of all dead letter scrobbles via API'); await (client as AbstractScrobbleClient).processDeadLetterQueue(1000); @@ -313,7 +313,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: const deadId = id as string; - (client as AbstractScrobbleClient).logger.debug(`User requested processing of dead letter scrobble ${deadId} via API call`) + (client as AbstractScrobbleClient).logger.verbose(`User requested processing of dead letter scrobble ${deadId} via API call`) const deadScrobble = (client as AbstractScrobbleClient).deadLetterScrobbles.find(x => x.id === deadId); @@ -337,7 +337,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: scrobbleClient: client, } = req; - (client as AbstractScrobbleClient).logger.debug('User requested deletion of all dead letter scrobbles via API'); + (client as AbstractScrobbleClient).logger.verbose('User requested deletion of all dead letter scrobbles via API'); (client as AbstractScrobbleClient).removeDeadLetterScrobbles(); @@ -355,12 +355,12 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: const deadId = id as string; - (client as AbstractScrobbleClient).logger.debug(`User requested removal of dead letter scrobble ${deadId} via API call`) + (client as AbstractScrobbleClient).logger.verbose(`User requested removal of dead letter scrobble ${deadId} via API call`) const deadScrobble = (client as AbstractScrobbleClient).deadLetterScrobbles.find(x => x.id === deadId); if(deadScrobble === undefined) { - (client as AbstractScrobbleClient).logger.debug(`No dead letter scrobble with ID ${deadId}`) + (client as AbstractScrobbleClient).logger.verbose(`No dead letter scrobble with ID ${deadId}`) return res.status(404).send(); } @@ -386,10 +386,10 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: 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'); + source.logger.verbose('User requested (re)start via API call'); if (!source.canPoll) { - source.logger.debug(`Does not support polling (${source.type})`); + source.logger.warn(`Does not support polling (${source.type})`); return res.status(400).send(`Specified source cannot poll (${source.type})`); } @@ -409,7 +409,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: 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'); + client.logger.verbose('User requested (re)start via API call'); client.logger.info('Checking (and trying) to stop scrobbler if already running...'); if(false === (await client.tryStopScrobbling())) { diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index a93c6351..b54e58f2 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -123,9 +123,9 @@ export default abstract class AbstractSource implements Authenticatable { return; } if (res === true) { - this.logger.debug('Building required data init succeeded'); + this.logger.verbose('Building required data init succeeded'); } else if (typeof res === 'string') { - this.logger.debug(`Building required data init succeeded => ${res}`); + this.logger.verbose(`Building required data init succeeded => ${res}`); } this.buildOK = true; } catch (e) { diff --git a/src/backend/sources/ChromecastSource.ts b/src/backend/sources/ChromecastSource.ts index fcd6c9a3..ad0cd367 100644 --- a/src/backend/sources/ChromecastSource.ts +++ b/src/backend/sources/ChromecastSource.ts @@ -126,7 +126,7 @@ export class ChromecastSource extends MemorySource { this.discoverDevices(logPayload); if(useAutoDiscovery) { - this.logger.debug('Will run mDNS discovery on subsequent heartbeats.') + this.logger.verbose('Will run mDNS discovery on subsequent heartbeats.') } return true; diff --git a/src/backend/sources/JRiverSource.ts b/src/backend/sources/JRiverSource.ts index 526eff2f..43df3f2d 100644 --- a/src/backend/sources/JRiverSource.ts +++ b/src/backend/sources/JRiverSource.ts @@ -59,7 +59,7 @@ export class JRiverSource extends MemorySource { url } = {} } = this.config; - this.logger.debug(`Config URL: '${url ?? '(None Given)'}' => Normalized: '${this.url.toString()}'`) + this.logger.verbose(`Config URL: '${url ?? '(None Given)'}' => Normalized: '${this.url.toString()}'`) return true; } diff --git a/src/backend/sources/KodiSource.ts b/src/backend/sources/KodiSource.ts index ca58cb19..33e29c1e 100644 --- a/src/backend/sources/KodiSource.ts +++ b/src/backend/sources/KodiSource.ts @@ -33,7 +33,7 @@ export class KodiSource extends MemorySource { } = {} } = this.config; 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()}'`) + this.logger.verbose(`Config URL: '${url ?? '(None Given)'}' => Normalized: '${this.client.url.toString()}'`) return true; } diff --git a/src/backend/sources/MemorySource.ts b/src/backend/sources/MemorySource.ts index 196db4c6..6c0b4206 100644 --- a/src/backend/sources/MemorySource.ts +++ b/src/backend/sources/MemorySource.ts @@ -169,7 +169,7 @@ export default class MemorySource extends AbstractSource { const matchingRecent = this.existingDiscovered(candidate); //sRecentlyPlayed.find(x => playObjDataMatch(x, candidate)); if (matchingRecent === undefined) { if(this.playerSourceOfTruth === SOURCE_SOT.PLAYER) { - player.logger.debug(`${stPrefix} added after ${thresholdResultSummary(thresholdResults)} and not matching any prior plays`); + player.logger.verbose(`${stPrefix} added after ${thresholdResultSummary(thresholdResults)} and not matching any prior plays`); } newStatefulPlays.push(candidate); } else { @@ -179,7 +179,7 @@ export default class MemorySource extends AbstractSource { if (duration !== undefined) { if (playDate.isAfter(rplayDate.add(duration, 's'))) { if(this.playerSourceOfTruth === SOURCE_SOT.PLAYER) { - player.logger.debug(`${stPrefix} added after ${thresholdResultSummary(thresholdResults)} and having a different timestamp than a prior play`); + player.logger.verbose(`${stPrefix} added after ${thresholdResultSummary(thresholdResults)} and having a different timestamp than a prior play`); } newStatefulPlays.push(candidate); } @@ -188,7 +188,7 @@ export default class MemorySource extends AbstractSource { if (discoveredPlays.length === 0 || !playObjDataMatch(discoveredPlays[0], candidate)) { // if most recent stateful play is not this track we'll add it if(this.playerSourceOfTruth === SOURCE_SOT.PLAYER) { - player.logger.debug(`${stPrefix} added after ${thresholdResultSummary(thresholdResults)}. Matched other recent play but could not determine time frame due to missing duration. Allowed due to not being last played track.`); + player.logger.verbose(`${stPrefix} added after ${thresholdResultSummary(thresholdResults)}. Matched other recent play but could not determine time frame due to missing duration. Allowed due to not being last played track.`); } newStatefulPlays.push(candidate); } diff --git a/src/backend/sources/MopidySource.ts b/src/backend/sources/MopidySource.ts index 369671f3..30b8ed08 100644 --- a/src/backend/sources/MopidySource.ts +++ b/src/backend/sources/MopidySource.ts @@ -101,7 +101,7 @@ export class MopidySource extends MemorySource { url } = {} } = this.config; - this.logger.debug(`Config URL: '${url ?? '(None Given)'}' => Normalized: '${this.url.toString()}'`) + this.logger.verbose(`Config URL: '${url ?? '(None Given)'}' => Normalized: '${this.url.toString()}'`) return true; } diff --git a/src/backend/sources/PlayerState/AbstractPlayerState.ts b/src/backend/sources/PlayerState/AbstractPlayerState.ts index 787654d8..8f6fb129 100644 --- a/src/backend/sources/PlayerState/AbstractPlayerState.ts +++ b/src/backend/sources/PlayerState/AbstractPlayerState.ts @@ -255,14 +255,14 @@ export abstract class AbstractPlayerState { if (position === oldEndProgress.position && !['paused', 'stopped'].includes(this.calculatedStatus)) { this.calculatedStatus = this.reportedStatus === 'stopped' ? CALCULATED_PLAYER_STATUSES.stopped : CALCULATED_PLAYER_STATUSES.paused; if (this.reportedStatus !== this.calculatedStatus) { - this.logger.verbose(`Reported status '${this.reportedStatus}' but track position has not progressed between two updates. Calculated player status is now ${this.calculatedStatus}`); + this.logger.debug(`Reported status '${this.reportedStatus}' but track position has not progressed between two updates. Calculated player status is now ${this.calculatedStatus}`); } else { this.logger.debug(`Player position is equal between current -> last update. Updated calculated status to ${this.calculatedStatus}`); } } else if (position !== oldEndProgress.position && this.calculatedStatus !== 'playing') { this.calculatedStatus = CALCULATED_PLAYER_STATUSES.playing; if (this.reportedStatus !== this.calculatedStatus) { - this.logger.verbose(`Reported status '${this.reportedStatus}' but track position has progressed between two updates. Calculated player status is now ${this.calculatedStatus}`); + this.logger.debug(`Reported status '${this.reportedStatus}' but track position has progressed between two updates. Calculated player status is now ${this.calculatedStatus}`); } else { this.logger.debug(`Player position changed between current -> last update. Updated calculated status to ${this.calculatedStatus}`); } diff --git a/src/backend/sources/ScrobbleSources.ts b/src/backend/sources/ScrobbleSources.ts index 70d72f5b..8ef0aaec 100644 --- a/src/backend/sources/ScrobbleSources.ts +++ b/src/backend/sources/ScrobbleSources.ts @@ -384,10 +384,10 @@ export default class ScrobbleSources { if(validConfig.configureAs === 'source') { configs.push(parsedConfig); } else { - this.logger.debug(`${sourceType} has 'configureAs: client' so will skip adding as a source`); + this.logger.verbose(`${sourceType} has 'configureAs: client' so will skip adding as a source`); } } else { - this.logger.debug(`${sourceType} did not have 'configureAs' specified! Assuming 'client' so will skip adding as a source`); + this.logger.verbose(`${sourceType} did not have 'configureAs' specified! Assuming 'client' so will skip adding as a source`); } } } catch (e: any) { -- 2.51.2 From 7170b0958637e73b7b8b41a3a9b92f5ef77c8192 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 11 Apr 2024 09:38:55 -0400 Subject: [PATCH 13/13] 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 ed8767db..c2f9d66d 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 685266a7..4058219c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "multi-scrobbler", - "version": "0.7.0", + "version": "0.7.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "multi-scrobbler", - "version": "0.7.0", + "version": "0.7.1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 48ac9330..b57273e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "multi-scrobbler", - "version": "0.7.0", + "version": "0.7.1", "type": "module", "description": "scrobble plays from multiple sources to multiple clients", "scripts": { -- 2.51.2