[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
0

Configure Feed

Select the types of activity you want to include in your feed.

refactor: optimize imports

* Remove unused imports
* Normalize spaces in curly braces
* Sort by module (npm packages, then relative imports)

FoxxMD (Mar 28, 2024, 9:20 AM EDT) eb62c2ce 7e6eddce

+572 -603
+7 -9
src/backend/index.ts
··· 1 1 import 'dotenv/config'; 2 - import {LogDataPretty, Logger} from "@foxxmd/logging"; 2 + import { childLogger, LogDataPretty, Logger as FoxLogger } from "@foxxmd/logging"; 3 3 import dayjs from 'dayjs'; 4 - import utc from 'dayjs/plugin/utc.js'; 4 + import duration from 'dayjs/plugin/duration.js'; 5 5 import isBetween from 'dayjs/plugin/isBetween.js'; 6 6 import relativeTime from 'dayjs/plugin/relativeTime.js'; 7 - import duration from 'dayjs/plugin/duration.js'; 8 7 import timezone from 'dayjs/plugin/timezone.js'; 9 - import { parseBool, readJson, sleep } from "./utils.js"; 8 + import utc from 'dayjs/plugin/utc.js'; 10 9 import * as path from "path"; 10 + import { SimpleIntervalJob, ToadScheduler } from "toad-scheduler"; 11 11 import { projectDir } from "./common/index.js"; 12 - import SpotifySource from "./sources/SpotifySource.js"; 13 12 import { AIOConfig } from "./common/infrastructure/config/aioConfig.js"; 13 + import { appLogger, initLogger as getInitLogger } from "./common/logging.js"; 14 14 import { getRoot } from "./ioc.js"; 15 - import {appLogger, initLogger as getInitLogger} from "./common/logging.js"; 16 15 import { initServer } from "./server/index.js"; 17 - import {SimpleIntervalJob, ToadScheduler} from "toad-scheduler"; 18 - import { createHeartbeatSourcesTask } from "./tasks/heartbeatSources.js"; 19 16 import { createHeartbeatClientsTask } from "./tasks/heartbeatClients.js"; 20 - import {loggerDebug, childLogger, LogData, Logger as FoxLogger} from '@foxxmd/logging'; 17 + import { createHeartbeatSourcesTask } from "./tasks/heartbeatSources.js"; 18 + import { parseBool, readJson, sleep } from "./utils.js"; 21 19 22 20 dayjs.extend(utc) 23 21 dayjs.extend(isBetween);
+9 -9
src/backend/ioc.ts
··· 1 - import {createContainer} from "iti"; 1 + import { Logger } from "@foxxmd/logging"; 2 + import { EventEmitter } from "events"; 3 + import fs from 'fs'; 4 + import { createContainer } from "iti"; 5 + import normalizeUrl from 'normalize-url'; 2 6 import path from "path"; 3 - import { configDir, projectDir } from "./common/index.js"; 4 - import ScrobbleClients from "./scrobblers/ScrobbleClients.js"; 5 - import ScrobbleSources from "./sources/ScrobbleSources.js"; 6 - import { Notifiers } from "./notifier/Notifiers.js"; 7 - import {EventEmitter} from "events"; 7 + import { projectDir } from "./common/index.js"; 8 8 import { logPath } from "./common/logging.js"; 9 9 import { WildcardEmitter } from "./common/WildcardEmitter.js"; 10 - import normalizeUrl from 'normalize-url'; 11 - import fs from 'fs'; 12 - import {Logger} from "@foxxmd/logging"; 10 + import { Notifiers } from "./notifier/Notifiers.js"; 11 + import ScrobbleClients from "./scrobblers/ScrobbleClients.js"; 12 + import ScrobbleSources from "./sources/ScrobbleSources.js"; 13 13 14 14 let version = 'Unknown'; 15 15
+15 -16
src/backend/utils.ts
··· 1 - import {accessSync, constants, promises} from "fs"; 2 - import dayjs, {Dayjs} from 'dayjs'; 3 - import utc from 'dayjs/plugin/utc.js'; 4 - import {Logger} from '@foxxmd/logging'; 5 - import JSON5 from 'json5'; 6 - import {TimeoutError, WebapiError} from "spotify-web-api-node/src/response-error.js"; 7 - import {Schema} from 'ajv'; 1 + import { Logger } from '@foxxmd/logging'; 2 + import backoffStrategies from '@kenyip/backoff-strategies'; 3 + import address from "address"; 8 4 import * as AjvNS from 'ajv'; 9 - import Ajv from 'ajv'; 5 + import Ajv, { Schema } from 'ajv'; 6 + import { replaceResultTransformer, stripIndentTransformer, TemplateTag, trimResultTransformer } from 'common-tags'; 7 + import dayjs, { Dayjs } from 'dayjs'; 8 + import { Duration } from "dayjs/plugin/duration.js"; 9 + import utc from 'dayjs/plugin/utc.js'; 10 + import { Request } from "express"; 11 + import { accessSync, constants, promises } from "fs"; 12 + import JSON5 from 'json5'; 13 + import pathUtil from "path"; 14 + import { getErrorCause } from "pony-cause"; 15 + import { TimeoutError, WebapiError } from "spotify-web-api-node/src/response-error.js"; 16 + import { PlayObject } from "../core/Atomic.js"; 10 17 import { 11 18 asPlayerStateData, 12 19 NO_DEVICE, ··· 19 26 RemoteIdentityParts, 20 27 ScrobbleThresholdResult, 21 28 } from "./common/infrastructure/Atomic.js"; 22 - import {Request} from "express"; 23 - import pathUtil from "path"; 24 - import {getErrorCause} from "pony-cause"; 25 - import backoffStrategies from '@kenyip/backoff-strategies'; 26 - import {replaceResultTransformer, stripIndentTransformer, TemplateTag, trimResultTransformer} from 'common-tags'; 27 - import {Duration} from "dayjs/plugin/duration.js"; 28 - import { PlayObject } from "../core/Atomic.js"; 29 - import address from "address"; 30 29 31 30 //const { default: Ajv } = AjvNS; 32 31 dayjs.extend(utc);
+3 -3
src/core/Atomic.ts
··· 1 - import {Dayjs} from "dayjs"; 2 - import {ListenProgress} from "../backend/sources/PlayerState/ListenProgress.js"; 3 - import {LogDataPretty, LogLevel} from "@foxxmd/logging"; 1 + import { LogDataPretty, LogLevel } from "@foxxmd/logging"; 2 + import { Dayjs } from "dayjs"; 3 + import { ListenProgress } from "../backend/sources/PlayerState/ListenProgress.js"; 4 4 5 5 export interface SourceStatusData { 6 6 status: string;
+10 -10
src/backend/common/logging.ts
··· 1 - import path from "path"; 2 - import { projectDir } from "./index.js"; 3 - import process from "process"; 4 1 import { 5 - parseLogOptions, 6 - loggerAppRolling, 7 - LogOptions as FoxLogOptions, 2 + childLogger, 8 3 Logger as FoxLogger, 9 - childLogger, LogLevel, PrettyOptionsExtra, 4 + loggerAppRolling, 5 + LogLevel, 6 + LogOptions as FoxLogOptions, 7 + parseLogOptions, 10 8 } from '@foxxmd/logging'; 11 - import {PassThrough, Transform} from "node:stream"; 12 - import {buildLogger, buildDestinationStdout, buildDestinationJsonPrettyStream} from "@foxxmd/logging/factory"; 13 - import {parseBool} from "../utils.js"; 9 + import { buildDestinationJsonPrettyStream, buildDestinationStdout, buildLogger } from "@foxxmd/logging/factory"; 10 + import { PassThrough, Transform } from "node:stream"; 11 + import path from "path"; 12 + import process from "process"; 13 + import { projectDir } from "./index.js"; 14 14 15 15 export let logPath = path.resolve(projectDir, `./logs`); 16 16 if (typeof process.env.CONFIG_DIR === 'string') {
+1 -2
src/backend/notifier/AbstractWebhookNotifier.ts
··· 1 + import { childLogger, Logger } from "@foxxmd/logging"; 1 2 import { GotifyConfig, NtfyConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; 2 - import {childLogger, Logger} from "@foxxmd/logging"; 3 - import { mergeArr } from "../utils.js"; 4 3 5 4 export abstract class AbstractWebhookNotifier { 6 5
+5 -5
src/backend/notifier/GotifyWebhookNotifier.ts
··· 1 - import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; 2 - import { GotifyConfig, PrioritiesConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; 3 - import {gotify} from 'gotify'; 1 + import { Logger } from "@foxxmd/logging"; 2 + import { HTTPError } from "got"; 3 + import { gotify } from 'gotify'; 4 4 import request from 'superagent'; 5 - import {HTTPError} from "got"; 6 - import {Logger} from "@foxxmd/logging"; 5 + import { GotifyConfig, PrioritiesConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; 6 + import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; 7 7 8 8 export class GotifyWebhookNotifier extends AbstractWebhookNotifier { 9 9
+8 -3
src/backend/notifier/Notifiers.ts
··· 1 - import {childLogger, Logger} from '@foxxmd/logging'; 2 - import { GotifyConfig, NtfyConfig, WebhookConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; 1 + import { childLogger, Logger } from '@foxxmd/logging'; 2 + import { EventEmitter } from "events"; 3 + import { 4 + GotifyConfig, 5 + NtfyConfig, 6 + WebhookConfig, 7 + WebhookPayload 8 + } from "../common/infrastructure/config/health/webhooks.js"; 3 9 import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; 4 10 import { GotifyWebhookNotifier } from "./GotifyWebhookNotifier.js"; 5 11 import { NtfyWebhookNotifier } from "./NtfyWebhookNotifier.js"; 6 - import {EventEmitter} from "events"; 7 12 8 13 export class Notifiers { 9 14
+5 -5
src/backend/notifier/NtfyWebhookNotifier.ts
··· 1 - import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; 2 - import { NtfyConfig, PrioritiesConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; 3 - import {publish} from 'ntfy'; 1 + import { Logger } from "@foxxmd/logging"; 2 + import { publish } from 'ntfy'; 3 + import { Config } from "ntfy/interfaces.js"; 4 4 import request from "superagent"; 5 - import {Logger} from "@foxxmd/logging"; 6 - import {Config} from "ntfy/interfaces.js"; 5 + import { NtfyConfig, PrioritiesConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; 6 + import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; 7 7 8 8 export class NtfyWebhookNotifier extends AbstractWebhookNotifier { 9 9
+23 -25
src/backend/scrobblers/AbstractScrobbleClient.ts
··· 1 - import dayjs, {Dayjs} from "dayjs"; 1 + import { childLogger, Logger } from "@foxxmd/logging"; 2 + import dayjs, { Dayjs } from "dayjs"; 3 + import EventEmitter from "events"; 4 + import { FixedSizeList } from 'fixed-size-list'; 5 + import { nanoid } from "nanoid"; 6 + import { messageWithCauses } from "pony-cause"; 2 7 import { 3 - comparingMultipleArtists, 4 - mergeArr, 5 - playObjDataMatch, 6 - pollingBackoff, 7 - setIntersection, 8 - sleep, 9 - sortByOldestPlayDate, 10 - } from "../utils.js"; 8 + DeadLetterScrobble, 9 + PlayObject, 10 + QueuedScrobble, 11 + TA_CLOSE, 12 + TA_FUZZY, 13 + TrackStringOptions, 14 + } from "../../core/Atomic.js"; 15 + import { buildTrackString, capitalize, truncateStringToLength } from "../../core/StringUtils.js"; 16 + import { hasNodeNetworkException } from "../common/errors/NodeErrors.js"; 17 + import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; 11 18 import { 12 19 ARTIST_WEIGHT, 13 20 Authenticatable, ··· 19 26 INITIALIZING, 20 27 InitState, 21 28 NOT_INITIALIZED, 22 - REFERENCE_WEIGHT, 23 29 ScrobbledPlayObject, 24 30 TIME_WEIGHT, 25 31 TITLE_WEIGHT, 26 32 } from "../common/infrastructure/Atomic.js"; 27 - import {childLogger, Logger} from "@foxxmd/logging"; 28 33 import { CommonClientConfig } from "../common/infrastructure/config/client/index.js"; 29 34 import { Notifiers } from "../notifier/Notifiers.js"; 30 - import {FixedSizeList} from 'fixed-size-list'; 31 35 import { 32 - DeadLetterScrobble, 33 - PlayObject, 34 - QueuedScrobble, 35 - TA_CLOSE, 36 - TA_FUZZY, 37 - TrackStringOptions, 38 - } from "../../core/Atomic.js"; 39 - import { buildTrackString, capitalize, truncateStringToLength } from "../../core/StringUtils.js"; 40 - import EventEmitter from "events"; 36 + comparingMultipleArtists, 37 + playObjDataMatch, 38 + pollingBackoff, 39 + setIntersection, 40 + sleep, 41 + sortByOldestPlayDate, 42 + } from "../utils.js"; 41 43 import { compareScrobbleArtists, compareScrobbleTracks, normalizeStr } from "../utils/StringUtils.js"; 42 - import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; 43 - import {nanoid} from "nanoid"; 44 - import {messageWithCauses} from "pony-cause"; 45 - import { hasNodeNetworkException } from "../common/errors/NodeErrors.js"; 46 44 import { 47 45 comparePlayTemporally, 48 46 temporalAccuracyIsAtLeast,
+10 -19
src/backend/scrobblers/LastfmScrobbler.ts
··· 1 - import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; 1 + import { Logger } from "@foxxmd/logging"; 2 2 import dayjs from 'dayjs'; 3 - 4 - import { 5 - playObjDataMatch, 6 - removeUndefinedKeys, 7 - setIntersection, 8 - sleep, 9 - sortByOldestPlayDate, 10 - } from "../utils.js"; 11 - import LastfmApiClient from "../common/vendor/LastfmApiClient.js"; 12 - import { FormatPlayObjectOptions, INITIALIZING, ScrobbledPlayObject } from "../common/infrastructure/Atomic.js"; 13 - import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; 14 - import {TrackScrobblePayload, TrackScrobbleResponse, UserGetRecentTracksResponse} from "lastfm-node-client"; 15 - import { Notifiers } from "../notifier/Notifiers.js"; 16 - import {Logger} from "@foxxmd/logging"; 17 - import { PlayObject, TrackStringOptions } from "../../core/Atomic.js"; 18 - import { buildTrackString, capitalize } from "../../core/StringUtils.js"; 19 3 import EventEmitter from "events"; 20 - import { UpstreamError } from "../common/errors/UpstreamError.js"; 4 + import { TrackScrobbleResponse, UserGetRecentTracksResponse } from "lastfm-node-client"; 5 + import { PlayObject } from "../../core/Atomic.js"; 6 + import { buildTrackString, capitalize } from "../../core/StringUtils.js"; 21 7 import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 22 - import { getScrobbleTsSOCDate } from "../utils/TimeUtils.js"; 8 + import { UpstreamError } from "../common/errors/UpstreamError.js"; 9 + import { FormatPlayObjectOptions, INITIALIZING } from "../common/infrastructure/Atomic.js"; 10 + import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; 11 + import LastfmApiClient from "../common/vendor/LastfmApiClient.js"; 12 + import { Notifiers } from "../notifier/Notifiers.js"; 13 + import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; 23 14 24 15 export default class LastfmScrobbler extends AbstractScrobbleClient { 25 16
+9 -9
src/backend/scrobblers/ListenbrainzScrobbler.ts
··· 1 + import { Logger } from "@foxxmd/logging"; 1 2 import dayjs from 'dayjs'; 2 - 3 - import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; 3 + import EventEmitter from "events"; 4 + import { PlayObject } from "../../core/Atomic.js"; 5 + import { buildTrackString, capitalize } from "../../core/StringUtils.js"; 6 + import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 7 + import { UpstreamError } from "../common/errors/UpstreamError.js"; 4 8 import { FormatPlayObjectOptions, INITIALIZING } from "../common/infrastructure/Atomic.js"; 5 - import { Notifiers } from "../notifier/Notifiers.js"; 6 - import {Logger} from "@foxxmd/logging"; 7 9 import { ListenBrainzClientConfig } from "../common/infrastructure/config/client/listenbrainz.js"; 8 10 import { ListenbrainzApiClient, ListenPayload } from "../common/vendor/ListenbrainzApiClient.js"; 9 - import { PlayObject, TrackStringOptions } from "../../core/Atomic.js"; 10 - import { buildTrackString, capitalize } from "../../core/StringUtils.js"; 11 - import EventEmitter from "events"; 12 - import { UpstreamError } from "../common/errors/UpstreamError.js"; 13 - import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 11 + import { Notifiers } from "../notifier/Notifiers.js"; 12 + 13 + import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; 14 14 15 15 export default class ListenbrainzScrobbler extends AbstractScrobbleClient { 16 16
+13 -13
src/backend/scrobblers/MalojaScrobbler.ts
··· 1 - import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; 2 - import request, {ResponseError, SuperAgentRequest} from 'superagent'; 3 - import dayjs from 'dayjs'; 1 + import { Logger } from "@foxxmd/logging"; 4 2 import compareVersions from 'compare-versions'; 5 - import { sleep, parseRetryAfterSecsFromObj } from "../utils.js"; 3 + import dayjs from 'dayjs'; 4 + import EventEmitter from "events"; 5 + import normalizeUrl from "normalize-url"; 6 + import request, { SuperAgentRequest } from 'superagent'; 7 + import { PlayObject } from "../../core/Atomic.js"; 8 + import { buildTrackString, capitalize } from "../../core/StringUtils.js"; 9 + import { isSuperAgentResponseError } from "../common/errors/ErrorUtils.js"; 10 + import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 11 + import { UpstreamError } from "../common/errors/UpstreamError.js"; 6 12 import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, INITIALIZING } from "../common/infrastructure/Atomic.js"; 7 13 import { MalojaClientConfig } from "../common/infrastructure/config/client/maloja.js"; 8 - import { Notifiers } from "../notifier/Notifiers.js"; 9 - import {Logger} from "@foxxmd/logging"; 10 14 import { 11 15 getMalojaResponseError, 12 16 isMalojaAPIErrorBody, ··· 19 23 MalojaV2ScrobbleData, 20 24 MalojaV3ScrobbleData, 21 25 } from "../common/vendor/maloja/interfaces.js"; 22 - import { PlayObject, TrackStringOptions } from "../../core/Atomic.js"; 23 - import { buildTrackString, capitalize } from "../../core/StringUtils.js"; 24 - import EventEmitter from "events"; 25 - import normalizeUrl from "normalize-url"; 26 - import { UpstreamError } from "../common/errors/UpstreamError.js"; 26 + import { Notifiers } from "../notifier/Notifiers.js"; 27 + import { parseRetryAfterSecsFromObj, sleep } from "../utils.js"; 27 28 import { getScrobbleTsSOCDate, getScrobbleTsSOCDateWithContext } from "../utils/TimeUtils.js"; 28 - import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 29 - import { isSuperAgentResponseError } from "../common/errors/ErrorUtils.js"; 29 + import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; 30 30 31 31 const feat = ["ft.", "ft", "feat.", "feat", "featuring", "Ft.", "Ft", "Feat.", "Feat", "Featuring"]; 32 32
+13 -16
src/backend/scrobblers/ScrobbleClients.ts
··· 1 1 /* eslint-disable no-case-declarations */ 2 - import dayjs, {Dayjs} from "dayjs"; 3 - import { 4 - readJson, 5 - validateJson, 6 - } from "../utils.js"; 7 - import MalojaScrobbler from "./MalojaScrobbler.js"; 8 - import LastfmScrobbler from "./LastfmScrobbler.js"; 2 + import { childLogger, Logger } from '@foxxmd/logging'; 3 + import dayjs, { Dayjs } from "dayjs"; 4 + import { PlayObject } from "../../core/Atomic.js"; 9 5 import { clientTypes, ConfigMeta } from "../common/infrastructure/Atomic.js"; 10 6 import { AIOConfig } from "../common/infrastructure/config/aioConfig.js"; 7 + import { ClientAIOConfig, ClientConfig } from "../common/infrastructure/config/client/clients.js"; 8 + import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; 9 + import { ListenBrainzClientConfig } from "../common/infrastructure/config/client/listenbrainz.js"; 10 + import { MalojaClientConfig } from "../common/infrastructure/config/client/maloja.js"; 11 11 import * as aioSchema from '../common/schema/aio-client.json'; 12 12 import * as clientSchema from '../common/schema/client.json'; 13 - import { ClientAIOConfig, ClientConfig } from "../common/infrastructure/config/client/clients.js"; 14 - import { MalojaClientConfig } from "../common/infrastructure/config/client/maloja.js"; 15 - import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; 16 - import { Notifiers } from "../notifier/Notifiers.js"; 17 - import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; 18 - import {childLogger, Logger} from '@foxxmd/logging'; 19 - import ListenbrainzScrobbler from "./ListenbrainzScrobbler.js"; 20 - import { ListenBrainzClientConfig } from "../common/infrastructure/config/client/listenbrainz.js"; 21 - import { PlayObject } from "../../core/Atomic.js"; 22 13 import { WildcardEmitter } from "../common/WildcardEmitter.js"; 14 + import { Notifiers } from "../notifier/Notifiers.js"; 15 + import { readJson, validateJson, } from "../utils.js"; 16 + import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; 17 + import LastfmScrobbler from "./LastfmScrobbler.js"; 18 + import ListenbrainzScrobbler from "./ListenbrainzScrobbler.js"; 19 + import MalojaScrobbler from "./MalojaScrobbler.js"; 23 20 24 21 type groupedNamedConfigs = {[key: string]: ParsedConfig[]}; 25 22
+21 -20
src/backend/server/api.ts
··· 1 - import {ExpressWithAsync} from "@awaitjs/express"; 2 - import { getRoot } from "../ioc.js"; 3 - import { makeClientCheckMiddle, makeSourceCheckMiddle } from "./middleware.js"; 4 - import AbstractSource from "../sources/AbstractSource.js"; 1 + import { ExpressWithAsync } from "@awaitjs/express"; 2 + import { LogDataPretty, Logger, LogLevel } from "@foxxmd/logging"; 3 + import bsseDef from 'better-sse'; 4 + import bodyParser from "body-parser"; 5 + import { FixedSizeList } from 'fixed-size-list'; 6 + import { PassThrough } from "node:stream"; 7 + import { Transform } from "stream"; 5 8 import { 6 9 ClientStatusData, 7 - DeadLetterScrobble, LeveledLogData, 10 + DeadLetterScrobble, 11 + LeveledLogData, 8 12 LogOutputConfig, 9 - PlayObject, SOURCE_SOT, 13 + PlayObject, 14 + SOURCE_SOT, 10 15 SourceStatusData, 11 16 } from "../../core/Atomic.js"; 12 - import {LogDataPretty, Logger, LogLevel} from "@foxxmd/logging"; 13 - import {Transform} from "stream"; 14 - import * as bsse from 'better-sse'; 15 - import bsseDef from 'better-sse'; 16 - import { setupTautulliRoutes } from "./tautulliRoutes.js"; 17 - import { setupPlexRoutes } from "./plexRoutes.js"; 18 - import { setupJellyfinRoutes } from "./jellyfinRoutes.js"; 19 - import { setupDeezerRoutes } from "./deezerRoutes.js"; 20 - import { setupAuthRoutes } from "./auth.js"; 21 - import {ExpressHandler} from "../common/infrastructure/Atomic.js"; 22 - import MemorySource from "../sources/MemorySource.js"; 23 17 import { capitalize } from "../../core/StringUtils.js"; 18 + import { ExpressHandler } from "../common/infrastructure/Atomic.js"; 19 + import { getRoot } from "../ioc.js"; 24 20 import AbstractScrobbleClient from "../scrobblers/AbstractScrobbleClient.js"; 21 + import AbstractSource from "../sources/AbstractSource.js"; 22 + import MemorySource from "../sources/MemorySource.js"; 25 23 import { sortByNewestPlayDate } from "../utils.js"; 26 - import bodyParser from "body-parser"; 24 + import { setupAuthRoutes } from "./auth.js"; 25 + import { setupDeezerRoutes } from "./deezerRoutes.js"; 26 + import { setupJellyfinRoutes } from "./jellyfinRoutes.js"; 27 + import { makeClientCheckMiddle, makeSourceCheckMiddle } from "./middleware.js"; 28 + import { setupPlexRoutes } from "./plexRoutes.js"; 29 + import { setupTautulliRoutes } from "./tautulliRoutes.js"; 27 30 import { setupWebscrobblerRoutes } from "./webscrobblerRoutes.js"; 28 - import {FixedSizeList} from 'fixed-size-list'; 29 - import {PassThrough} from "node:stream"; 30 31 31 32 const maxBufferSize = 300; 32 33 const output: Record<number, FixedSizeList<LogDataPretty>> = {};
+4 -4
src/backend/server/auth.ts
··· 1 - import {ExpressWithAsync} from "@awaitjs/express"; 2 - import {Logger} from "@foxxmd/logging"; 3 - import ScrobbleSources from "../sources/ScrobbleSources.js"; 1 + import { ExpressWithAsync } from "@awaitjs/express"; 2 + import { Logger } from "@foxxmd/logging"; 4 3 import passport from "passport"; 5 4 import { ExpressHandler } from "../common/infrastructure/Atomic.js"; 6 - import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; 7 5 import LastfmScrobbler from "../scrobblers/LastfmScrobbler.js"; 6 + import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; 8 7 import LastfmSource from "../sources/LastfmSource.js"; 8 + import ScrobbleSources from "../sources/ScrobbleSources.js"; 9 9 import SpotifySource from "../sources/SpotifySource.js"; 10 10 11 11 export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMiddle: ExpressHandler, clientMiddle: ExpressHandler, scrobbleSources: ScrobbleSources, scrobbleClients: ScrobbleClients) => {
+5 -8
src/backend/server/deezerRoutes.ts
··· 1 - import { ExpressHandler } from "../common/infrastructure/Atomic.js"; 2 - import { mergeArr, parseBool, sleep } from "../utils.js"; 3 - import {ExpressWithAsync} from "@awaitjs/express"; 4 - import {Logger} from "@foxxmd/logging"; 5 - import ScrobbleSources from "../sources/ScrobbleSources.js"; 6 - import PlexSource, { plexRequestMiddle } from "../sources/PlexSource.js"; 7 - import { PlexNotifier } from "../sources/ingressNotifiers/PlexNotifier.js"; 8 - import DeezerSource from "../sources/DeezerSource.js"; 1 + import { ExpressWithAsync } from "@awaitjs/express"; 2 + import { Logger } from "@foxxmd/logging"; 9 3 import passport from "passport"; 4 + import DeezerSource from "../sources/DeezerSource.js"; 5 + import ScrobbleSources from "../sources/ScrobbleSources.js"; 6 + import { sleep } from "../utils.js"; 10 7 11 8 export const setupDeezerRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { 12 9
+8 -9
src/backend/server/index.ts
··· 1 - import {addAsync, Router} from '@awaitjs/express'; 2 - import express from 'express'; 3 - import ViteExpress from "vite-express"; 1 + import { addAsync, Router } from '@awaitjs/express'; 2 + import { childLogger, LogDataPretty, Logger } from "@foxxmd/logging"; 4 3 import bodyParser from 'body-parser'; 5 - import passport from 'passport'; 4 + import { stripIndents } from "common-tags"; 5 + import express from 'express'; 6 6 import session from 'express-session'; 7 + import { PassThrough } from "node:stream"; 8 + import passport from 'passport'; 9 + import ViteExpress from "vite-express"; 7 10 import { getRoot } from "../ioc.js"; 11 + import { getAddress, parseBool } from "../utils.js"; 8 12 import { setupApi } from "./api.js"; 9 - import { getAddress, mergeArr, parseBool } from "../utils.js"; 10 - import {stripIndents} from "common-tags"; 11 - import {childLogger, LogData, LogDataPretty} from "@foxxmd/logging"; 12 - import {PassThrough} from "node:stream"; 13 - import {Logger} from '@foxxmd/logging'; 14 13 15 14 const app = addAsync(express()); 16 15 const router = Router();
+4 -4
src/backend/server/jellyfinRoutes.ts
··· 1 - import { parseBool, remoteHostIdentifiers } from "../utils.js"; 2 - import {ExpressWithAsync} from "@awaitjs/express"; 3 - import {Logger} from "@foxxmd/logging"; 4 - import ScrobbleSources from "../sources/ScrobbleSources.js"; 1 + import { ExpressWithAsync } from "@awaitjs/express"; 2 + import { Logger } from "@foxxmd/logging"; 5 3 import bodyParser from "body-parser"; 6 4 import { JellyfinNotifier } from "../sources/ingressNotifiers/JellyfinNotifier.js"; 7 5 import JellyfinSource from "../sources/JellyfinSource.js"; 6 + import ScrobbleSources from "../sources/ScrobbleSources.js"; 7 + import { parseBool, remoteHostIdentifiers } from "../utils.js"; 8 8 9 9 export const setupJellyfinRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { 10 10
+1 -1
src/backend/server/middleware.ts
··· 1 + import { Logger } from "@foxxmd/logging"; 1 2 import { ExpressHandler } from "../common/infrastructure/Atomic.js"; 2 - import {Logger} from "@foxxmd/logging"; 3 3 4 4 export const makeSourceCheckMiddle = (sources: any) => (required: boolean ): ExpressHandler => (req: any, res: any, next: any) => { 5 5 const {
+4 -5
src/backend/server/plexRoutes.ts
··· 1 + import { ExpressWithAsync } from "@awaitjs/express"; 2 + import { childLogger, Logger } from "@foxxmd/logging"; 1 3 import { ExpressHandler } from "../common/infrastructure/Atomic.js"; 2 - import { mergeArr, parseBool } from "../utils.js"; 3 - import {ExpressWithAsync} from "@awaitjs/express"; 4 - import {childLogger, Logger} from "@foxxmd/logging"; 5 - import ScrobbleSources from "../sources/ScrobbleSources.js"; 6 - import PlexSource, { plexRequestMiddle } from "../sources/PlexSource.js"; 7 4 import { PlexNotifier } from "../sources/ingressNotifiers/PlexNotifier.js"; 5 + import PlexSource, { plexRequestMiddle } from "../sources/PlexSource.js"; 6 + import ScrobbleSources from "../sources/ScrobbleSources.js"; 8 7 9 8 export const setupPlexRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { 10 9
+4 -4
src/backend/server/tautulliRoutes.ts
··· 1 - import { TautulliNotifier } from "../sources/ingressNotifiers/TautulliNotifier.js"; 1 + import { ExpressWithAsync } from "@awaitjs/express"; 2 + import { Logger } from "@foxxmd/logging"; 2 3 import { ExpressHandler } from "../common/infrastructure/Atomic.js"; 4 + import { TautulliNotifier } from "../sources/ingressNotifiers/TautulliNotifier.js"; 5 + import ScrobbleSources from "../sources/ScrobbleSources.js"; 3 6 import TautulliSource from "../sources/TautulliSource.js"; 4 7 import { parseBool } from "../utils.js"; 5 - import {ExpressWithAsync} from "@awaitjs/express"; 6 - import {Logger} from "@foxxmd/logging"; 7 - import ScrobbleSources from "../sources/ScrobbleSources.js"; 8 8 9 9 export const setupTautulliRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { 10 10
+6 -8
src/backend/server/webscrobblerRoutes.ts
··· 1 - import { mergeArr, parseBool, remoteHostIdentifiers } from "../utils.js"; 2 - import {ExpressWithAsync} from "@awaitjs/express"; 3 - import {childLogger, Logger} from "@foxxmd/logging"; 4 - import ScrobbleSources from "../sources/ScrobbleSources.js"; 1 + import { ExpressWithAsync } from "@awaitjs/express"; 2 + import { childLogger, Logger } from "@foxxmd/logging"; 5 3 import bodyParser from "body-parser"; 6 - import { WebScrobblerPayload } from "../common/vendor/webscrobbler/interfaces.js"; 7 - import { WebhookNotifier } from "../sources/ingressNotifiers/WebhookNotifier.js"; 8 - import { nonEmptyBody } from "./middleware.js"; 9 - import { WebScrobblerSource } from "../sources/WebScrobblerSource.js"; 10 4 import path from "path"; 5 + import { WebhookNotifier } from "../sources/ingressNotifiers/WebhookNotifier.js"; 6 + import ScrobbleSources from "../sources/ScrobbleSources.js"; 7 + import { WebScrobblerSource } from "../sources/WebScrobblerSource.js"; 8 + import { nonEmptyBody } from "./middleware.js"; 11 9 12 10 export const setupWebscrobblerRoutes = (app: ExpressWithAsync, parentLogger: Logger, scrobbleSources: ScrobbleSources) => { 13 11
+17 -21
src/backend/sources/AbstractSource.ts
··· 1 - import dayjs, {Dayjs} from "dayjs"; 2 - import { 3 - genGroupId, 4 - genGroupIdStrFromPlay, 5 - mergeArr, 6 - playObjDataMatch, 7 - pollingBackoff, 8 - sleep, 9 - sortByNewestPlayDate, 10 - sortByOldestPlayDate, 11 - findCauseByFunc, 12 - formatNumber, 13 - } from "../utils.js"; 1 + import { childLogger, Logger } from '@foxxmd/logging'; 2 + import dayjs, { Dayjs } from "dayjs"; 3 + import { EventEmitter } from "events"; 4 + import { FixedSizeList } from "fixed-size-list"; 5 + import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; 6 + import { buildTrackString, capitalize } from "../../core/StringUtils.js"; 7 + import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 14 8 import { 15 9 Authenticatable, 16 10 DEFAULT_POLLING_INTERVAL, ··· 18 12 DEFAULT_RETRY_MULTIPLIER, 19 13 DeviceId, 20 14 GroupedFixedPlays, 21 - GroupedPlays, 22 15 InternalConfig, 23 - NO_DEVICE, 24 16 NO_USER, 25 17 PlayPlatformId, 26 18 PlayUserId, ··· 28 20 SINGLE_USER_PLATFORM_ID, 29 21 SourceType, 30 22 } from "../common/infrastructure/Atomic.js"; 31 - import {childLogger, Logger} from '@foxxmd/logging'; 32 23 import { SourceConfig } from "../common/infrastructure/config/source/sources.js"; 33 - import {EventEmitter} from "events"; 34 - import {FixedSizeList} from "fixed-size-list"; 35 24 import TupleMap from "../common/TupleMap.js"; 36 - import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; 37 - import { buildTrackString, capitalize } from "../../core/StringUtils.js"; 38 - import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 25 + import { 26 + findCauseByFunc, 27 + formatNumber, 28 + genGroupId, 29 + playObjDataMatch, 30 + pollingBackoff, 31 + sleep, 32 + sortByNewestPlayDate, 33 + sortByOldestPlayDate, 34 + } from "../utils.js"; 39 35 import { comparePlayTemporally, temporalAccuracyIsAtLeast } from "../utils/TimeUtils.js"; 40 36 41 37 export interface RecentlyPlayedOptions {
+22 -19
src/backend/sources/ChromecastSource.ts
··· 1 - import MemorySource from "./MemorySource.js"; 2 - import { ChromecastSourceConfig } from "../common/infrastructure/config/source/chromecast.js"; 1 + import { createPlatform, Media, MediaController, PersistentClient } from "@foxxmd/chromecast-client"; 2 + import { ContextualValidationError } from "@foxxmd/chromecast-client/dist/cjs/src/utils.js"; 3 + import { childLogger, Logger } from "@foxxmd/logging"; 4 + import { Client as CastClient } from 'castv2'; 5 + import dayjs from "dayjs"; 6 + import { EventEmitter } from "events"; 7 + import e from "express"; 8 + import { findCauseByReference } from "pony-cause"; 9 + import { PlayObject } from "../../core/Atomic.js"; 10 + import { buildTrackString } from "../../core/StringUtils.js"; 11 + import { NETWORK_ERROR_FAILURE_CODES } from "../common/errors/NodeErrors.js"; 3 12 import { 4 13 FormatPlayObjectOptions, 5 14 InternalConfig, ··· 8 17 PlayerStateData, 9 18 SourceData, 10 19 } from "../common/infrastructure/Atomic.js"; 11 - import {EventEmitter} from "events"; 12 - import {MediaController, PersistentClient, Media, createPlatform} from "@foxxmd/chromecast-client"; 13 - import {Client as CastClient} from 'castv2'; 14 - import {findCauseByReference} from "pony-cause"; 15 - import { PlayObject } from "../../core/Atomic.js"; 16 - import dayjs from "dayjs"; 17 - import { RecentlyPlayedOptions } from "./AbstractSource.js"; 18 - import { difference, genGroupIdStr, isIPv4, mergeArr, parseBool, sleep } from "../utils.js"; 19 - import { PlatformApplication, PlatformApplicationWithContext, PlatformType } from "../common/vendor/chromecast/interfaces.js"; 20 + import { ChromecastSourceConfig } from "../common/infrastructure/config/source/chromecast.js"; 21 + import { MaybeLogger } from "../common/logging.js"; 20 22 import { 21 23 chromePlayerStateToReported, 22 24 genDeviceId, 25 + genPlayHash, 23 26 getCurrentPlatformApplications, 24 27 getMediaStatus, 25 - genPlayHash, 26 28 } from "../common/vendor/chromecast/ChromecastClientUtils.js"; 27 - import {Logger} from "@foxxmd/logging"; 28 - import {ContextualValidationError} from "@foxxmd/chromecast-client/dist/cjs/src/utils.js"; 29 - import { buildTrackString } from "../../core/StringUtils.js"; 29 + import { 30 + PlatformApplication, 31 + PlatformApplicationWithContext, 32 + PlatformType 33 + } from "../common/vendor/chromecast/interfaces.js"; 34 + import { difference, genGroupIdStr, parseBool } from "../utils.js"; 30 35 import { discoveryAvahi, discoveryNative } from "../utils/MDNSUtils.js"; 31 - import {MaybeLogger} from "../common/logging.js"; 32 - import e, {application} from "express"; 33 - import {NETWORK_ERROR_FAILURE_CODES} from "../common/errors/NodeErrors.js"; 34 - import {childLogger} from "@foxxmd/logging"; 36 + import { RecentlyPlayedOptions } from "./AbstractSource.js"; 37 + import MemorySource from "./MemorySource.js"; 35 38 36 39 interface ChromecastDeviceInfo { 37 40 mdns: MdnsDeviceInfo
+7 -13
src/backend/sources/DeezerSource.ts
··· 1 - import request from 'superagent'; 2 - import passport from "passport"; 3 - import { 4 - parseRetryAfterSecsFromObj, 5 - readJson, 6 - sleep, 7 - sortByOldestPlayDate, 8 - writeFile, 9 - } from "../utils.js"; 10 - import {Strategy as DeezerStrategy} from 'passport-deezer'; 11 - import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; 12 1 import dayjs from "dayjs"; 13 - import { DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; 14 - import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 15 2 import EventEmitter from "events"; 3 + import passport from "passport"; 4 + import { Strategy as DeezerStrategy } from 'passport-deezer'; 5 + import request from 'superagent'; 16 6 import { PlayObject } from "../../core/Atomic.js"; 7 + import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 8 + import { DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; 9 + import { parseRetryAfterSecsFromObj, readJson, sleep, sortByOldestPlayDate, writeFile, } from "../utils.js"; 10 + import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; 17 11 18 12 export default class DeezerSource extends AbstractSource { 19 13 workingCredsPath;
+6 -6
src/backend/sources/JRiverSource.ts
··· 1 - import MemorySource from "./MemorySource.js"; 2 - import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 3 1 import dayjs from "dayjs"; 4 - import {URL} from "url"; 2 + import { EventEmitter } from "events"; 5 3 import normalizeUrl from 'normalize-url'; 6 - import {EventEmitter} from "events"; 7 - import { RecentlyPlayedOptions } from "./AbstractSource.js"; 4 + import { URL } from "url"; 5 + import { PlayObject } from "../../core/Atomic.js"; 6 + import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 8 7 import { JRiverSourceConfig } from "../common/infrastructure/config/source/jriver.js"; 9 8 import { Info, JRiverApiClient, PLAYER_STATE } from "../common/vendor/JRiverApiClient.js"; 10 - import { PlayObject } from "../../core/Atomic.js"; 9 + import { RecentlyPlayedOptions } from "./AbstractSource.js"; 10 + import MemorySource from "./MemorySource.js"; 11 11 12 12 export class JRiverSource extends MemorySource { 13 13 declare config: JRiverSourceConfig;
+11 -12
src/backend/sources/JellyfinSource.ts
··· 1 - import MemorySource from "./MemorySource.js"; 2 - import dayjs, {Dayjs} from "dayjs"; 1 + import { Logger } from "@foxxmd/logging"; 2 + import dayjs from "dayjs"; 3 + import EventEmitter from "events"; 4 + import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; 5 + import { buildTrackString, splitByFirstFound, truncateStringToLength } from "../../core/StringUtils.js"; 6 + import { FormatPlayObjectOptions, InternalConfig, PlayPlatformId } from "../common/infrastructure/Atomic.js"; 7 + import { JellySourceConfig } from "../common/infrastructure/config/source/jellyfin.js"; 3 8 import { 4 9 combinePartsToString, 10 + doubleReturnNewline, 5 11 parseBool, 6 12 parseDurationFromTimestamp, 7 13 playObjDataMatch, 8 - doubleReturnNewline, 9 14 } from "../utils.js"; 10 - import { JellySourceConfig } from "../common/infrastructure/config/source/jellyfin.js"; 11 - import { FormatPlayObjectOptions, InternalConfig, PlayPlatformId } from "../common/infrastructure/Atomic.js"; 12 - import EventEmitter from "events"; 13 - import { PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; 14 - import {Logger} from "@foxxmd/logging"; 15 - import { JellyfinPlayerState } from "./PlayerState/JellyfinPlayerState.js"; 16 - import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; 17 - import { buildTrackString, splitByFirstFound, truncateStringToLength } from "../../core/StringUtils.js"; 18 - import {source} from "common-tags"; 19 15 import { 20 16 comparePlayTemporally, 21 17 temporalAccuracyIsAtLeast, 22 18 temporalPlayComparisonSummary, 23 19 } from "../utils/TimeUtils.js"; 20 + import MemorySource from "./MemorySource.js"; 21 + import { PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; 22 + import { JellyfinPlayerState } from "./PlayerState/JellyfinPlayerState.js"; 24 23 25 24 const shortDeviceId = truncateStringToLength(10, ''); 26 25
+4 -4
src/backend/sources/KodiSource.ts
··· 1 - import MemorySource from "./MemorySource.js"; 1 + import { EventEmitter } from "events"; 2 + import { PlayObject } from "../../core/Atomic.js"; 2 3 import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 3 - import {EventEmitter} from "events"; 4 - import { RecentlyPlayedOptions } from "./AbstractSource.js"; 5 4 import { KodiSourceConfig } from "../common/infrastructure/config/source/kodi.js"; 6 5 import { KodiApiClient } from "../common/vendor/KodiApiClient.js"; 7 - import { PlayObject } from "../../core/Atomic.js"; 6 + import { RecentlyPlayedOptions } from "./AbstractSource.js"; 7 + import MemorySource from "./MemorySource.js"; 8 8 9 9 export class KodiSource extends MemorySource { 10 10 declare config: KodiSourceConfig;
+9 -9
src/backend/sources/LastfmSource.ts
··· 1 - import { RecentlyPlayedOptions } from "./AbstractSource.js"; 1 + import dayjs from "dayjs"; 2 + import EventEmitter from "events"; 3 + import { TrackObject, UserGetRecentTracksResponse } from "lastfm-node-client"; 4 + import request from "superagent"; 5 + import { PlayObject, SOURCE_SOT } from "../../core/Atomic.js"; 6 + import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 7 + import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 8 + import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; 2 9 import LastfmApiClient from "../common/vendor/LastfmApiClient.js"; 3 10 import { sortByOldestPlayDate } from "../utils.js"; 4 - import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 5 - import {TrackObject, UserGetRecentTracksResponse} from "lastfm-node-client"; 6 - import EventEmitter from "events"; 7 - import {PlayObject, SOURCE_SOT} from "../../core/Atomic.js"; 11 + import { RecentlyPlayedOptions } from "./AbstractSource.js"; 8 12 import MemorySource from "./MemorySource.js"; 9 - import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; 10 - import dayjs from "dayjs"; 11 - import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 12 - import request from "superagent"; 13 13 14 14 export default class LastfmSource extends MemorySource { 15 15
+5 -5
src/backend/sources/ListenbrainzSource.ts
··· 1 - import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; 2 - import { FormatPlayObjectOptions, INITIALIZING, InternalConfig } from "../common/infrastructure/Atomic.js"; 3 1 import EventEmitter from "events"; 2 + import request from "superagent"; 3 + import { PlayObject, SOURCE_SOT } from "../../core/Atomic.js"; 4 + import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 5 + import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 4 6 import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz.js"; 5 7 import { ListenbrainzApiClient } from "../common/vendor/ListenbrainzApiClient.js"; 8 + import { RecentlyPlayedOptions } from "./AbstractSource.js"; 6 9 import MemorySource from "./MemorySource.js"; 7 - import request from "superagent"; 8 - import {isNodeNetworkException} from "../common/errors/NodeErrors.js"; 9 - import {PlayObject, SOURCE_SOT} from "../../core/Atomic.js"; 10 10 11 11 export default class ListenbrainzSource extends MemorySource { 12 12
+7 -7
src/backend/sources/MPRISSource.ts
··· 1 + import { Interfaces as Notifications } from '@dbus-types/notifications' 1 2 import dayjs from "dayjs"; 3 + import { DBusInterface, sessionBus } from 'dbus-ts'; 4 + import EventEmitter from "events"; 5 + import { PlayObject } from "../../core/Atomic.js"; 6 + import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 2 7 import { 3 8 MPRIS_IFACE, 4 9 MPRIS_PATH, ··· 8 13 PlaybackStatus, 9 14 PlayerInfo, 10 15 } from "../common/infrastructure/config/source/mpris.js"; 11 - import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 12 - import MemorySource from "./MemorySource.js"; 13 - import { RecentlyPlayedOptions } from "./AbstractSource.js"; 14 16 import { removeDuplicates } from "../utils.js"; 15 - import EventEmitter from "events"; 16 - import { PlayObject } from "../../core/Atomic.js"; 17 - import {DBusInterface, sessionBus} from 'dbus-ts'; 18 - import { Interfaces as Notifications } from '@dbus-types/notifications' 17 + import { RecentlyPlayedOptions } from "./AbstractSource.js"; 18 + import MemorySource from "./MemorySource.js"; 19 19 20 20 21 21 export class MPRISSource extends MemorySource {
+18 -26
src/backend/sources/MemorySource.ts
··· 1 - import AbstractSource from "./AbstractSource.js"; 2 - import { 3 - playObjDataMatch, 4 - sortByOldestPlayDate, 5 - toProgressAwarePlayObject, 6 - getProgress, 7 - thresholdResultSummary, 8 - genGroupId, 9 - genGroupIdStr, 10 - getPlatformIdFromData, 11 - formatNumber, 12 - } from "../utils.js"; 1 + import { Logger } from "@foxxmd/logging"; 13 2 import dayjs from "dayjs"; 3 + import { EventEmitter } from "events"; 4 + import objectHash from 'object-hash'; 5 + import { SimpleIntervalJob, Task, ToadScheduler } from "toad-scheduler"; 6 + import { PlayObject, SOURCE_SOT, SOURCE_SOT_TYPES, SourcePlayerObj } from "../../core/Atomic.js"; 7 + import { buildTrackString } from "../../core/StringUtils.js"; 14 8 import { 15 9 asPlayerStateData, 16 10 CALCULATED_PLAYER_STATUSES, 17 - DeviceId, 18 - GroupedPlays, 19 11 InternalConfig, 20 12 PlayerStateData, 21 13 PlayPlatformId, 22 - PlayUserId, 23 14 ProgressAwarePlayObject, 24 - ScrobbleThresholdResult, 25 15 SourceType, 26 16 } from "../common/infrastructure/Atomic.js"; 27 - import TupleMap from "../common/TupleMap.js"; 28 - import {AbstractPlayerState, createPlayerOptions, PlayerStateOptions} from "./PlayerState/AbstractPlayerState.js"; 29 - import { GenericPlayerState } from "./PlayerState/GenericPlayerState.js"; 30 - import {Logger} from "@foxxmd/logging"; 31 - import {PlayObject, SOURCE_SOT, SOURCE_SOT_TYPES, SourcePlayerObj} from "../../core/Atomic.js"; 32 - import { buildTrackString } from "../../core/StringUtils.js"; 33 - import {SimpleIntervalJob, Task, ToadScheduler} from "toad-scheduler"; 17 + import { PollingOptions } from "../common/infrastructure/config/common.js"; 34 18 import { SourceConfig } from "../common/infrastructure/config/source/sources.js"; 35 - import {EventEmitter} from "events"; 36 - import objectHash from 'object-hash'; 19 + import { 20 + formatNumber, 21 + genGroupId, 22 + genGroupIdStr, 23 + getPlatformIdFromData, 24 + playObjDataMatch, 25 + thresholdResultSummary, 26 + } from "../utils.js"; 37 27 import { timePassesScrobbleThreshold } from "../utils/TimeUtils.js"; 38 - import {PollingOptions} from "../common/infrastructure/config/common.js"; 28 + import AbstractSource from "./AbstractSource.js"; 29 + import { AbstractPlayerState, createPlayerOptions, PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; 30 + import { GenericPlayerState } from "./PlayerState/GenericPlayerState.js"; 39 31 40 32 export default class MemorySource extends AbstractSource { 41 33
+11 -11
src/backend/sources/MopidySource.ts
··· 1 - import MemorySource from "./MemorySource.js"; 2 - import { MopidySourceConfig } from "../common/infrastructure/config/source/mopidy.js"; 1 + import { loggerTest } from "@foxxmd/logging"; 2 + import dayjs from "dayjs"; 3 + import { EventEmitter } from "events"; 4 + import Mopidy, { models } from "mopidy"; 5 + import normalizeUrl from 'normalize-url'; 6 + import pEvent from 'p-event'; 7 + import { URL } from "url"; 8 + import { PlayObject } from "../../core/Atomic.js"; 9 + import { buildTrackString } from "../../core/StringUtils.js"; 3 10 import { 4 11 FormatPlayObjectOptions, 5 12 InternalConfig, 6 13 PlayerStateData, 7 14 SINGLE_USER_PLATFORM_ID, 8 15 } from "../common/infrastructure/Atomic.js"; 9 - import dayjs from "dayjs"; 10 - import Mopidy, {models} from "mopidy"; 11 - import {URL} from "url"; 12 - import normalizeUrl from 'normalize-url'; 13 - import {EventEmitter} from "events"; 14 - import pEvent from 'p-event'; 16 + import { MopidySourceConfig } from "../common/infrastructure/config/source/mopidy.js"; 15 17 import { RecentlyPlayedOptions } from "./AbstractSource.js"; 16 - import { PlayObject } from "../../core/Atomic.js"; 17 - import { buildTrackString } from "../../core/StringUtils.js"; 18 - import {loggerTest} from "@foxxmd/logging"; 18 + import MemorySource from "./MemorySource.js"; 19 19 20 20 export class MopidySource extends MemorySource { 21 21 declare config: MopidySourceConfig;
+7 -7
src/backend/sources/PlexSource.ts
··· 1 - import dayjs from "dayjs"; 2 - import { combinePartsToString } from "../utils.js"; 3 - import AbstractSource from "./AbstractSource.js"; 4 - import formidable from 'formidable'; 1 + import { childLogger, Logger } from "@foxxmd/logging"; 5 2 import concatStream from 'concat-stream'; 6 - import { PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; 7 - import { FormatPlayObjectOptions, InternalConfig, SourceType } from "../common/infrastructure/Atomic.js"; 3 + import dayjs from "dayjs"; 8 4 import EventEmitter from "events"; 5 + import formidable from 'formidable'; 9 6 import { PlayObject } from "../../core/Atomic.js"; 10 7 import { truncateStringToLength } from "../../core/StringUtils.js"; 11 - import {childLogger, Logger} from "@foxxmd/logging"; 8 + import { FormatPlayObjectOptions, InternalConfig, SourceType } from "../common/infrastructure/Atomic.js"; 9 + import { PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; 10 + import { combinePartsToString } from "../utils.js"; 11 + import AbstractSource from "./AbstractSource.js"; 12 12 13 13 const shortDeviceId = truncateStringToLength(10, ''); 14 14
+32 -33
src/backend/sources/ScrobbleSources.ts
··· 1 1 /* eslint-disable no-case-declarations */ 2 - import { mergeArr, parseBool, readJson, validateJson } from "../utils.js"; 3 - import SpotifySource from "./SpotifySource.js"; 4 - import PlexSource from "./PlexSource.js"; 5 - import TautulliSource from "./TautulliSource.js"; 6 - import { SubsonicSource } from "./SubsonicSource.js"; 7 - import JellyfinSource from "./JellyfinSource.js"; 8 - import LastfmSource from "./LastfmSource.js"; 9 - import DeezerSource from "./DeezerSource.js"; 10 - import { ConfigMeta, InternalConfig, SourceType, sourceTypes } from "../common/infrastructure/Atomic.js"; 2 + import { childLogger, Logger } from '@foxxmd/logging'; 3 + import EventEmitter from "events"; 11 4 import { configDir as defaultConfigDir } from "../common/index.js"; 12 - import {childLogger, Logger} from '@foxxmd/logging'; 13 - import { SourceAIOConfig, SourceConfig } from "../common/infrastructure/config/source/sources.js"; 5 + import { ConfigMeta, InternalConfig, SourceType, sourceTypes } from "../common/infrastructure/Atomic.js"; 6 + import { AIOConfig, SourceDefaults } from "../common/infrastructure/config/aioConfig.js"; 7 + import { ChromecastSourceConfig } from "../common/infrastructure/config/source/chromecast.js"; 14 8 import { DeezerData, DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; 15 - import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; 16 9 import { JellyData, JellySourceConfig } from "../common/infrastructure/config/source/jellyfin.js"; 10 + import { JRiverData, JRiverSourceConfig } from "../common/infrastructure/config/source/jriver.js"; 11 + import { KodiData, KodiSourceConfig } from "../common/infrastructure/config/source/kodi.js"; 12 + import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; 13 + import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz.js"; 14 + import { MopidySourceConfig } from "../common/infrastructure/config/source/mopidy.js"; 15 + import { MPRISData, MPRISSourceConfig } from "../common/infrastructure/config/source/mpris.js"; 16 + import { PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; 17 + import { SourceAIOConfig, SourceConfig } from "../common/infrastructure/config/source/sources.js"; 18 + import { SpotifySourceConfig, SpotifySourceData } from "../common/infrastructure/config/source/spotify.js"; 17 19 import { SubsonicData, SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js"; 18 20 import { TautulliSourceConfig } from "../common/infrastructure/config/source/tautulli.js"; 19 - import { PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; 20 - import { SpotifySourceConfig, SpotifySourceData } from "../common/infrastructure/config/source/spotify.js"; 21 - import AbstractSource from "./AbstractSource.js"; 22 - import { AIOConfig, SourceDefaults } from "../common/infrastructure/config/aioConfig.js"; 21 + import { WebScrobblerSourceConfig } from "../common/infrastructure/config/source/webscrobbler.js"; 22 + import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; 23 23 import * as aioSchema from "../common/schema/aio-source.json"; 24 24 import * as sourceSchema from "../common/schema/source.json"; 25 - import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; 26 - import YTMusicSource from "./YTMusicSource.js"; 27 - import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; 28 - import { MPRISData, MPRISSourceConfig } from "../common/infrastructure/config/source/mpris.js"; 29 - import { MPRISSource } from "./MPRISSource.js"; 30 - import EventEmitter from "events"; 31 - import { MopidySource } from "./MopidySource.js"; 32 - import { MopidySourceConfig } from "../common/infrastructure/config/source/mopidy.js"; 33 - import ListenbrainzSource from "./ListenbrainzSource.js"; 34 - import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz.js"; 35 - import { JRiverSource } from "./JRiverSource.js"; 36 - import { JRiverData, JRiverSourceConfig } from "../common/infrastructure/config/source/jriver.js"; 37 - import { KodiSource } from "./KodiSource.js"; 38 - import { KodiData, KodiSourceConfig } from "../common/infrastructure/config/source/kodi.js"; 39 25 import { WildcardEmitter } from "../common/WildcardEmitter.js"; 40 - import { WebScrobblerSource } from "./WebScrobblerSource.js"; 41 - import { WebScrobblerSourceConfig } from "../common/infrastructure/config/source/webscrobbler.js"; 26 + import { parseBool, readJson, validateJson } from "../utils.js"; 27 + import AbstractSource from "./AbstractSource.js"; 42 28 import { ChromecastSource } from "./ChromecastSource.js"; 43 - import { ChromecastSourceConfig } from "../common/infrastructure/config/source/chromecast.js"; 29 + import DeezerSource from "./DeezerSource.js"; 30 + import JellyfinSource from "./JellyfinSource.js"; 31 + import { JRiverSource } from "./JRiverSource.js"; 32 + import { KodiSource } from "./KodiSource.js"; 33 + import LastfmSource from "./LastfmSource.js"; 34 + import ListenbrainzSource from "./ListenbrainzSource.js"; 35 + import { MopidySource } from "./MopidySource.js"; 36 + import { MPRISSource } from "./MPRISSource.js"; 37 + import PlexSource from "./PlexSource.js"; 38 + import SpotifySource from "./SpotifySource.js"; 39 + import { SubsonicSource } from "./SubsonicSource.js"; 40 + import TautulliSource from "./TautulliSource.js"; 41 + import { WebScrobblerSource } from "./WebScrobblerSource.js"; 42 + import YTMusicSource from "./YTMusicSource.js"; 44 43 45 44 type groupedNamedConfigs = {[key: string]: ParsedConfig[]}; 46 45
+23 -23
src/backend/sources/SpotifySource.ts
··· 1 - import dayjs, {Dayjs} from "dayjs"; 2 - import { 3 - readJson, 4 - writeFile, 5 - sortByOldestPlayDate, 6 - sleep, 7 - parseRetryAfterSecsFromObj, 8 - combinePartsToString, 9 - findCauseByFunc, 10 - } from "../utils.js"; 1 + import dayjs, { Dayjs } from "dayjs"; 2 + import EventEmitter from "events"; 11 3 import SpotifyWebApi from "spotify-web-api-node"; 12 4 import request from 'superagent'; 13 - import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; 14 - import { SpotifySourceConfig } from "../common/infrastructure/config/source/spotify.js"; 5 + import { PlayObject, SCROBBLE_TS_SOC_END, SCROBBLE_TS_SOC_START, ScrobbleTsSOC } from "../../core/Atomic.js"; 6 + import { truncateStringToLength } from "../../core/StringUtils.js"; 7 + import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 8 + import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; 15 9 import { 16 10 DEFAULT_POLLING_INTERVAL, 17 11 FormatPlayObjectOptions, ··· 22 16 ReportedPlayerStatus, 23 17 SourceData, 24 18 } from "../common/infrastructure/Atomic.js"; 25 - import PlayHistoryObject = SpotifyApi.PlayHistoryObject; 26 - import EventEmitter from "events"; 27 - import CurrentlyPlayingObject = SpotifyApi.CurrentlyPlayingObject; 28 - import TrackObjectFull = SpotifyApi.TrackObjectFull; 29 - import ArtistObjectSimplified = SpotifyApi.ArtistObjectSimplified; 30 - import AlbumObjectSimplified = SpotifyApi.AlbumObjectSimplified; 31 - import UserDevice = SpotifyApi.UserDevice; 19 + import { SpotifySourceConfig } from "../common/infrastructure/config/source/spotify.js"; 20 + import { 21 + combinePartsToString, 22 + findCauseByFunc, 23 + parseRetryAfterSecsFromObj, 24 + readJson, 25 + sleep, 26 + sortByOldestPlayDate, 27 + writeFile, 28 + } from "../utils.js"; 29 + import { RecentlyPlayedOptions } from "./AbstractSource.js"; 32 30 import MemorySource from "./MemorySource.js"; 33 - import { PlayObject, SCROBBLE_TS_SOC_END, SCROBBLE_TS_SOC_START, ScrobbleTsSOC } from "../../core/Atomic.js"; 34 - import { buildTrackString, truncateStringToLength } from "../../core/StringUtils.js"; 35 - import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 36 - import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; 31 + import AlbumObjectSimplified = SpotifyApi.AlbumObjectSimplified; 32 + import ArtistObjectSimplified = SpotifyApi.ArtistObjectSimplified; 33 + import CurrentlyPlayingObject = SpotifyApi.CurrentlyPlayingObject; 34 + import PlayHistoryObject = SpotifyApi.PlayHistoryObject; 35 + import TrackObjectFull = SpotifyApi.TrackObjectFull; 36 + import UserDevice = SpotifyApi.UserDevice; 37 37 38 38 const scopes = ['user-read-recently-played', 'user-read-currently-playing', 'user-read-playback-state', 'user-read-playback-position']; 39 39 const state = 'random';
+9 -9
src/backend/sources/SubsonicSource.ts
··· 1 - import request, {Request, Response} from 'superagent'; 2 1 import * as crypto from 'crypto'; 3 2 import dayjs from "dayjs"; 4 3 import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js"; 5 - import {findCauseByFunc, parseRetryAfterSecsFromObj, removeDuplicates, sleep} from "../utils.js"; 6 - import MemorySource from "./MemorySource.js"; 7 - import { SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js"; 8 - import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 9 - import { RecentlyPlayedOptions } from "./AbstractSource.js"; 10 4 import EventEmitter from "events"; 5 + import request, { Request } from 'superagent'; 11 6 import { PlayObject } from "../../core/Atomic.js"; 12 - import {isNodeNetworkException} from "../common/errors/NodeErrors.js"; 13 - import {UpstreamError} from "../common/errors/UpstreamError.js"; 14 - import {getSubsonicResponse, SubsonicResponse, SubsonicResponseCommon} from "../common/vendor/subsonic/interfaces.js"; 7 + import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 8 + import { UpstreamError } from "../common/errors/UpstreamError.js"; 9 + import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 10 + import { SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js"; 11 + import { getSubsonicResponse, SubsonicResponse, SubsonicResponseCommon } from "../common/vendor/subsonic/interfaces.js"; 12 + import { findCauseByFunc, parseRetryAfterSecsFromObj, removeDuplicates, sleep } from "../utils.js"; 13 + import { RecentlyPlayedOptions } from "./AbstractSource.js"; 14 + import MemorySource from "./MemorySource.js"; 15 15 16 16 dayjs.extend(isSameOrAfter); 17 17
+5 -5
src/backend/sources/TautulliSource.ts
··· 1 1 import dayjs from "dayjs"; 2 - import PlexSource from "./PlexSource.js"; 3 - import { TautulliSourceConfig } from "../common/infrastructure/config/source/tautulli.js"; 4 - import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 5 - import { combinePartsToString } from "../utils.js"; 6 2 import EventEmitter from "events"; 7 - import {Request} from "express"; 3 + import { Request } from "express"; 8 4 import { PlayObject } from "../../core/Atomic.js"; 9 5 import { truncateStringToLength } from "../../core/StringUtils.js"; 6 + import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 7 + import { TautulliSourceConfig } from "../common/infrastructure/config/source/tautulli.js"; 8 + import { combinePartsToString } from "../utils.js"; 9 + import PlexSource from "./PlexSource.js"; 10 10 11 11 const shortDeviceId = truncateStringToLength(10, ''); 12 12
+9 -5
src/backend/sources/WebScrobblerSource.ts
··· 1 - import MemorySource from "./MemorySource.js"; 1 + import dayjs from "dayjs"; 2 + import EventEmitter from "events"; 3 + import { PlayObject, SOURCE_SOT } from "../../core/Atomic.js"; 2 4 import { 3 5 FormatPlayObjectOptions, 4 6 InternalConfig, ··· 7 9 REPORTED_PLAYER_STATUSES, 8 10 ReportedPlayerStatus, 9 11 } from "../common/infrastructure/Atomic.js"; 10 - import EventEmitter from "events"; 11 - import {PlayObject, SOURCE_SOT} from "../../core/Atomic.js"; 12 - import { WebScrobblerHookEvent, WebScrobblerPayload, WebScrobblerSong } from "../common/vendor/webscrobbler/interfaces.js"; 13 - import dayjs from "dayjs"; 14 12 import { WebScrobblerSourceConfig } from "../common/infrastructure/config/source/webscrobbler.js"; 13 + import { 14 + WebScrobblerHookEvent, 15 + WebScrobblerPayload, 16 + WebScrobblerSong 17 + } from "../common/vendor/webscrobbler/interfaces.js"; 18 + import MemorySource from "./MemorySource.js"; 15 19 16 20 export class WebScrobblerSource extends MemorySource { 17 21
+7 -7
src/backend/sources/YTMusicSource.ts
··· 1 - import YouTubeMusic from "youtube-music-ts-api"; 2 - import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; 3 - import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 4 - import {IYouTubeMusicAuthenticated} from "youtube-music-ts-api/interfaces-primary"; 5 1 import dayjs from "dayjs"; 6 - import { parseDurationFromTimestamp, playObjDataMatch } from "../utils.js"; 7 - import {IPlaylistDetail, ITrackDetail} from "youtube-music-ts-api/interfaces-supplementary"; 8 - import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; 9 2 import EventEmitter from "events"; 3 + import YouTubeMusic from "youtube-music-ts-api"; 4 + import { IYouTubeMusicAuthenticated } from "youtube-music-ts-api/interfaces-primary"; 5 + import { IPlaylistDetail, ITrackDetail } from "youtube-music-ts-api/interfaces-supplementary"; 10 6 import { PlayObject } from "../../core/Atomic.js"; 7 + import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 8 + import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; 9 + import { parseDurationFromTimestamp, playObjDataMatch } from "../utils.js"; 10 + import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; 11 11 12 12 export default class YTMusicSource extends AbstractSource { 13 13 apiInstance?: IYouTubeMusicAuthenticated
+3 -4
src/backend/tasks/heartbeatClients.ts
··· 1 - import {childLogger, Logger} from '@foxxmd/logging'; 2 - import { mergeArr } from "../utils.js"; 3 - import {AsyncTask} from "toad-scheduler"; 4 - import {PromisePool} from "@supercharge/promise-pool"; 1 + import { childLogger, Logger } from '@foxxmd/logging'; 2 + import { PromisePool } from "@supercharge/promise-pool"; 3 + import { AsyncTask } from "toad-scheduler"; 5 4 import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; 6 5 7 6 export const createHeartbeatClientsTask = (clients: ScrobbleClients, parentLogger: Logger) => {
+4 -5
src/backend/tasks/heartbeatSources.ts
··· 1 - import {childLogger, Logger} from '@foxxmd/logging'; 2 - import { mergeArr } from "../utils.js"; 3 - import {AsyncTask} from "toad-scheduler"; 4 - import {PromisePool} from "@supercharge/promise-pool"; 5 - import ScrobbleSources from "../sources/ScrobbleSources.js"; 1 + import { childLogger, Logger } from '@foxxmd/logging'; 2 + import { PromisePool } from "@supercharge/promise-pool"; 3 + import { AsyncTask } from "toad-scheduler"; 6 4 import { ChromecastSource } from "../sources/ChromecastSource.js"; 5 + import ScrobbleSources from "../sources/ScrobbleSources.js"; 7 6 8 7 export const createHeartbeatSourcesTask = (sources: ScrobbleSources, parentLogger: Logger) => { 9 8 const logger = childLogger(parentLogger, ['Heartbeat', 'Sources']);
+4 -4
src/backend/utils/MDNSUtils.ts
··· 1 - import {Logger} from "@foxxmd/logging"; 1 + import { Browser, Service, ServiceType } from "@astronautlabs/mdns"; 2 + import { Logger } from "@foxxmd/logging"; 2 3 import AvahiBrowser from 'avahi-browse'; 4 + import { MdnsDeviceInfo } from "../common/infrastructure/Atomic.js"; 3 5 import { MaybeLogger } from "../common/logging.js"; 4 6 import { sleep } from "../utils.js"; 5 - import { MdnsDeviceInfo } from "../common/infrastructure/Atomic.js"; 6 - import {Browser, Service, ServiceType} from "@astronautlabs/mdns"; 7 - import {debounce, DebouncedFunction} from "./debounce.js"; 7 + import { debounce, DebouncedFunction } from "./debounce.js"; 8 8 9 9 export interface AvahiService { 10 10 service_name: string
+2 -2
src/backend/utils/StringUtils.ts
··· 1 + import { strategies, stringSameness, StringSamenessResult } from "@foxxmd/string-sameness"; 2 + import { PlayObject } from "../../core/Atomic.js"; 1 3 import { DELIMITERS } from "../common/infrastructure/Atomic.js"; 2 4 import { parseRegexSingleOrFail } from "../utils.js"; 3 - import { PlayObject } from "../../core/Atomic.js"; 4 - import {stringSameness, StringSamenessResult, strategies} from "@foxxmd/string-sameness"; 5 5 6 6 const {levenStrategy, diceStrategy} = strategies; 7 7
+3 -3
src/backend/utils/TimeUtils.ts
··· 1 + import dayjs, { Dayjs } from "dayjs"; 1 2 import { 2 3 PlayObject, 3 4 SCROBBLE_TS_SOC_END, ··· 10 11 TemporalAccuracy, 11 12 TemporalPlayComparison, 12 13 } from "../../core/Atomic.js"; 14 + import { capitalize } from "../../core/StringUtils.js"; 13 15 import { 14 16 DEFAULT_SCROBBLE_DURATION_THRESHOLD, 15 17 DEFAULT_SCROBBLE_PERCENT_THRESHOLD, 16 18 lowGranularitySources, 17 19 ScrobbleThresholdResult, 18 20 } from "../common/infrastructure/Atomic.js"; 19 - import { formatNumber } from "../utils.js"; 20 21 import { ScrobbleThresholds } from "../common/infrastructure/config/source/index.js"; 21 - import { capitalize } from "../../core/StringUtils.js"; 22 - import dayjs, {Dayjs} from "dayjs"; 22 + import { formatNumber } from "../utils.js"; 23 23 24 24 export const temporalPlayComparisonSummary = (data: TemporalPlayComparison, existingPlay?: PlayObject, candidatePlay?: PlayObject) => { 25 25 const parts: string[] = [];
+1 -1
src/backend/common/errors/ErrorUtils.ts
··· 1 - import {ResponseError} from "superagent"; 1 + import { ResponseError } from "superagent"; 2 2 3 3 export const isSuperAgentResponseError = (e: any): e is ResponseError => { 4 4 return typeof e === 'object'
+1 -1
src/backend/common/errors/NodeErrors.ts
··· 1 + import { findCauseByFunc } from "../../utils.js"; 1 2 import { isArbitraryObject } from "../infrastructure/Atomic.js"; 2 3 import ErrnoException = NodeJS.ErrnoException; 3 - import { findCauseByFunc } from "../../utils.js"; 4 4 5 5 export type NodeNetworkErrorCode = 'ENOTFOUND' | 'ETIMEDOUT' | 'EAI_AGAIN' | 'ECONNRESET' | 'ECONNREFUSED' | 'ERRADDRINUSE' | 'EADDRNOTAVAIL' | 'ECONNABORTED' | 'EHOSTUNREACH'; 6 6 export const NETWORK_ERROR_CODES: NodeNetworkErrorCode[] = ['ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN', 'ECONNRESET', 'ECONNREFUSED', 'ERRADDRINUSE', 'EADDRNOTAVAIL', 'ECONNABORTED', 'EHOSTUNREACH'];
+1 -1
src/backend/common/errors/UpstreamError.ts
··· 1 + import { Response } from 'superagent'; 1 2 import { findCauseByFunc } from "../../utils.js"; 2 - import {Response} from 'superagent'; 3 3 4 4 export class UpstreamError<T = undefined> extends Error { 5 5
+6 -6
src/backend/common/infrastructure/Atomic.ts
··· 1 - import {Dayjs} from "dayjs"; 2 - import {FixedSizeList} from 'fixed-size-list'; 3 - import {Logger} from '@foxxmd/logging'; 1 + import { Logger } from '@foxxmd/logging'; 2 + import { Dayjs } from "dayjs"; 3 + import { Request, Response } from "express"; 4 + import { NextFunction, ParamsDictionary, Query } from "express-serve-static-core"; 5 + import { FixedSizeList } from 'fixed-size-list'; 6 + import { PlayMeta, PlayObject } from "../../../core/Atomic.js"; 4 7 import TupleMap from "../TupleMap.js"; 5 - import {Request, Response} from "express"; 6 - import {NextFunction, ParamsDictionary, Query} from "express-serve-static-core"; 7 - import {PlayMeta, PlayObject} from "../../../core/Atomic.js"; 8 8 9 9 export type SourceType = 'spotify' | 'plex' | 'tautulli' | 'subsonic' | 'jellyfin' | 'lastfm' | 'deezer' | 'ytmusic' | 'mpris' | 'mopidy' | 'listenbrainz' | 'jriver' | 'kodi' | 'webscrobbler' | 'chromecast'; 10 10 export const sourceTypes: SourceType[] = ['spotify', 'plex', 'tautulli', 'subsonic', 'jellyfin', 'lastfm', 'deezer', 'ytmusic', 'mpris', 'mopidy', 'listenbrainz', 'jriver', 'kodi', 'webscrobbler', 'chromecast'];
+2 -2
src/backend/common/vendor/AbstractApiClient.ts
··· 1 - import {childLogger, Logger} from "@foxxmd/logging"; 2 - import {AbstractApiOptions, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; 1 + import { childLogger, Logger } from "@foxxmd/logging"; 3 2 import { PlayObject } from "../../../core/Atomic.js"; 4 3 import { capitalize } from "../../../core/StringUtils.js"; 4 + import { AbstractApiOptions, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; 5 5 6 6 export default abstract class AbstractApiClient { 7 7 name: string;
+4 -4
src/backend/common/vendor/JRiverApiClient.ts
··· 1 - import AbstractApiClient from "./AbstractApiClient.js"; 2 - import {JRiverData} from "../infrastructure/config/source/jriver.js"; 3 - import request, {Request, Response} from 'superagent'; 1 + import request, { Request, Response } from 'superagent'; 4 2 import xml2js from 'xml2js'; 5 - import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER} from "../infrastructure/Atomic.js"; 3 + import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER } from "../infrastructure/Atomic.js"; 4 + import { JRiverData } from "../infrastructure/config/source/jriver.js"; 5 + import AbstractApiClient from "./AbstractApiClient.js"; 6 6 7 7 const parser = new xml2js.Parser({'async': true}); 8 8
+6 -6
src/backend/common/vendor/KodiApiClient.ts
··· 1 - import AbstractApiClient from "./AbstractApiClient.js"; 2 - import { KodiData } from "../infrastructure/config/source/kodi.js"; 1 + import dayjs from "dayjs"; 3 2 import { KodiClient } from 'kodi-api' 4 3 import normalizeUrl from "normalize-url"; 5 - import {URL} from "url"; 6 - import { RecentlyPlayedOptions } from "../../sources/AbstractSource.js"; 7 - import {AbstractApiOptions, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; 8 - import dayjs from "dayjs"; 4 + import { URL } from "url"; 9 5 import { PlayObject } from "../../../core/Atomic.js"; 6 + import { RecentlyPlayedOptions } from "../../sources/AbstractSource.js"; 7 + import { AbstractApiOptions, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; 8 + import { KodiData } from "../infrastructure/config/source/kodi.js"; 9 + import AbstractApiClient from "./AbstractApiClient.js"; 10 10 11 11 interface KodiDuration { 12 12 hours: number
+9 -9
src/backend/common/vendor/LastfmApiClient.ts
··· 1 + import dayjs from "dayjs"; 1 2 import LastFm, { 2 3 AuthGetSessionResponse, 3 4 NowPlayingResponse, ··· 5 6 TrackScrobblePayload, 6 7 UserGetInfoResponse 7 8 } from "lastfm-node-client"; 8 - import AbstractApiClient from "./AbstractApiClient.js"; 9 - import dayjs from "dayjs"; 10 - import {readJson, removeUndefinedKeys, sleep, writeFile} from "../../utils.js"; 11 - import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; 12 - import { LastfmData } from "../infrastructure/config/client/lastfm.js"; 13 9 import { PlayObject } from "../../../core/Atomic.js"; 14 - import {getNodeNetworkException, isNodeNetworkException} from "../errors/NodeErrors.js"; 15 - import {nonEmptyStringOrDefault, splitByFirstFound} from "../../../core/StringUtils.js"; 16 - import {getScrobbleTsSOCDate} from "../../utils/TimeUtils.js"; 17 - import {UpstreamError} from "../errors/UpstreamError.js"; 10 + import { nonEmptyStringOrDefault, splitByFirstFound } from "../../../core/StringUtils.js"; 11 + import { readJson, removeUndefinedKeys, sleep, writeFile } from "../../utils.js"; 12 + import { getScrobbleTsSOCDate } from "../../utils/TimeUtils.js"; 13 + import { getNodeNetworkException, isNodeNetworkException } from "../errors/NodeErrors.js"; 14 + import { UpstreamError } from "../errors/UpstreamError.js"; 15 + import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; 16 + import { LastfmData } from "../infrastructure/config/client/lastfm.js"; 17 + import AbstractApiClient from "./AbstractApiClient.js"; 18 18 19 19 const badErrors = [ 20 20 'api key suspended',
+7 -7
src/backend/common/vendor/ListenbrainzApiClient.ts
··· 1 - import AbstractApiClient from "./AbstractApiClient.js"; 2 - import request, {Request} from 'superagent'; 3 - import { ListenBrainzClientData } from "../infrastructure/config/client/listenbrainz.js"; 4 - import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; 5 - import dayjs from "dayjs"; 6 1 import { stringSameness } from '@foxxmd/string-sameness'; 7 - import { combinePartsToString } from "../../utils.js"; 2 + import dayjs from "dayjs"; 3 + import request, { Request } from 'superagent'; 8 4 import { PlayObject } from "../../../core/Atomic.js"; 9 5 import { slice } from "../../../core/StringUtils.js"; 6 + import { combinePartsToString } from "../../utils.js"; 10 7 import { 11 8 findDelimiters, 12 9 normalizeStr, ··· 15 12 parseTrackCredits, 16 13 uniqueNormalizedStrArr, 17 14 } from "../../utils/StringUtils.js"; 18 - import { UpstreamError } from "../errors/UpstreamError.js"; 19 15 import { getScrobbleTsSOCDate } from "../../utils/TimeUtils.js"; 16 + import { UpstreamError } from "../errors/UpstreamError.js"; 17 + import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; 18 + import { ListenBrainzClientData } from "../infrastructure/config/client/listenbrainz.js"; 19 + import AbstractApiClient from "./AbstractApiClient.js"; 20 20 21 21 22 22 export interface ArtistMBIDMapping {
+5 -6
src/backend/sources/PlayerState/AbstractPlayerState.ts
··· 1 + import { childLogger, Logger } from "@foxxmd/logging"; 2 + import dayjs, { Dayjs } from "dayjs"; 3 + import { PlayObject, Second, SOURCE_SOT, SOURCE_SOT_TYPES, SourcePlayerObj } from "../../../core/Atomic.js"; 4 + import { buildTrackString } from "../../../core/StringUtils.js"; 1 5 import { 2 6 CALCULATED_PLAYER_STATUSES, 3 7 CalculatedPlayerStatus, ··· 5 9 REPORTED_PLAYER_STATUSES, 6 10 ReportedPlayerStatus, 7 11 } from "../../common/infrastructure/Atomic.js"; 8 - import dayjs, {Dayjs} from "dayjs"; 12 + import { PollingOptions } from "../../common/infrastructure/config/common.js"; 9 13 import { formatNumber, genGroupIdStr, playObjDataMatch, progressBar } from "../../utils.js"; 10 - import {childLogger, Logger} from "@foxxmd/logging"; 11 14 import { ListenProgress } from "./ListenProgress.js"; 12 - import {PlayObject, Second, SOURCE_SOT, SOURCE_SOT_TYPES, SourcePlayerObj} from "../../../core/Atomic.js"; 13 - import { buildTrackString } from "../../../core/StringUtils.js"; 14 15 import { ListenRange } from "./ListenRange.js"; 15 - import {id} from "common-tags"; 16 - import {PollingOptions} from "../../common/infrastructure/config/common.js"; 17 16 18 17 export interface PlayerStateIntervals { 19 18 staleInterval?: number
+2 -2
src/backend/sources/PlayerState/GenericPlayerState.ts
··· 1 - import { AbstractPlayerState, PlayerStateOptions } from "./AbstractPlayerState.js"; 2 - import {Logger} from "@foxxmd/logging"; 1 + import { Logger } from "@foxxmd/logging"; 3 2 import { PlayPlatformId } from "../../common/infrastructure/Atomic.js"; 3 + import { AbstractPlayerState, PlayerStateOptions } from "./AbstractPlayerState.js"; 4 4 5 5 export class GenericPlayerState extends AbstractPlayerState { 6 6 constructor(logger: Logger, platformId: PlayPlatformId, opts?: PlayerStateOptions) {
+3 -3
src/backend/sources/PlayerState/JellyfinPlayerState.ts
··· 1 - import { GenericPlayerState } from "./GenericPlayerState.js"; 2 - import {Logger} from "@foxxmd/logging"; 1 + import { Logger } from "@foxxmd/logging"; 2 + import { PlayObject } from "../../../core/Atomic.js"; 3 3 import { PlayPlatformId, ReportedPlayerStatus } from "../../common/infrastructure/Atomic.js"; 4 4 import { PlayerStateOptions } from "./AbstractPlayerState.js"; 5 - import { PlayObject } from "../../../core/Atomic.js"; 5 + import { GenericPlayerState } from "./GenericPlayerState.js"; 6 6 7 7 export class JellyfinPlayerState extends GenericPlayerState { 8 8 constructor(logger: Logger, platformId: PlayPlatformId, opts?: PlayerStateOptions) {
+1 -1
src/backend/sources/PlayerState/ListenProgress.ts
··· 1 - import dayjs, {Dayjs} from "dayjs"; 1 + import dayjs, { Dayjs } from "dayjs"; 2 2 3 3 import { PlayProgress, Second } from "../../../core/Atomic.js"; 4 4
+2 -2
src/backend/sources/PlayerState/ListenRange.ts
··· 1 - import { ListenProgress } from "./ListenProgress.js"; 2 - import dayjs, {Dayjs} from "dayjs"; 1 + import dayjs, { Dayjs } from "dayjs"; 3 2 import { ListenRangeData, Second } from "../../../core/Atomic.js"; 3 + import { ListenProgress } from "./ListenProgress.js"; 4 4 5 5 export class ListenRange implements ListenRangeData { 6 6
+3 -3
src/backend/sources/ingressNotifiers/IngressNotifier.ts
··· 1 - import {childLogger, Logger} from '@foxxmd/logging'; 2 - import { remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; 3 - import {Request} from "express"; 1 + import { childLogger, Logger } from '@foxxmd/logging'; 2 + import { Request } from "express"; 4 3 import { RemoteIdentityParts } from "../../common/infrastructure/Atomic.js"; 4 + import { remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; 5 5 6 6 7 7 export abstract class IngressNotifier {
+4 -4
src/backend/sources/ingressNotifiers/JellyfinNotifier.ts
··· 1 - import { IngressNotifier } from "./IngressNotifier.js"; 2 - import {Request} from "express"; 3 - import JellyfinSource from "../JellyfinSource.js"; 1 + import { Logger } from "@foxxmd/logging"; 2 + import { Request } from "express"; 4 3 import { remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; 5 - import {Logger} from "@foxxmd/logging"; 4 + import JellyfinSource from "../JellyfinSource.js"; 5 + import { IngressNotifier } from "./IngressNotifier.js"; 6 6 7 7 export class JellyfinNotifier extends IngressNotifier { 8 8
+3 -3
src/backend/sources/ingressNotifiers/PlexNotifier.ts
··· 1 - import { IngressNotifier } from "./IngressNotifier.js"; 2 - import {Request} from "express"; 1 + import { Logger } from "@foxxmd/logging"; 2 + import { Request } from "express"; 3 3 import PlexSource from "../PlexSource.js"; 4 - import {Logger} from "@foxxmd/logging"; 4 + import { IngressNotifier } from "./IngressNotifier.js"; 5 5 6 6 export class PlexNotifier extends IngressNotifier { 7 7
+3 -4
src/backend/sources/ingressNotifiers/TautulliNotifier.ts
··· 1 - import { IngressNotifier } from "./IngressNotifier.js"; 2 - import {Request} from "express"; 3 - import PlexSource from "../PlexSource.js"; 1 + import { Logger } from "@foxxmd/logging"; 2 + import { Request } from "express"; 4 3 import TautulliSource from "../TautulliSource.js"; 5 - import {Logger} from "@foxxmd/logging"; 4 + import { IngressNotifier } from "./IngressNotifier.js"; 6 5 7 6 export class TautulliNotifier extends IngressNotifier { 8 7
+3 -3
src/backend/sources/ingressNotifiers/WebhookNotifier.ts
··· 1 - import { IngressNotifier } from "./IngressNotifier.js"; 2 - import {Request} from "express"; 1 + import { Logger } from "@foxxmd/logging"; 2 + import { Request } from "express"; 3 3 import path from "path"; 4 - import {Logger} from "@foxxmd/logging"; 4 + import { IngressNotifier } from "./IngressNotifier.js"; 5 5 6 6 export class WebhookNotifier extends IngressNotifier { 7 7
+6 -6
src/backend/tests/jellyfin/jellyfin.test.ts
··· 1 - import {describe, it} from 'mocha'; 2 - import {assert} from 'chai'; 3 - import samplePayload from './playbackProgressSample.json'; 1 + import { loggerTest } from "@foxxmd/logging"; 2 + import { assert } from 'chai'; 3 + import EventEmitter from "events"; 4 + import { describe, it } from 'mocha'; 5 + import { JsonPlayObject } from "../../../core/Atomic.js"; 4 6 5 7 import JellyfinSource from "../../sources/JellyfinSource.js"; 6 - import EventEmitter from "events"; 7 - import { JsonPlayObject, PlayObject } from "../../../core/Atomic.js"; 8 - import {loggerTest} from "@foxxmd/logging"; 8 + import samplePayload from './playbackProgressSample.json'; 9 9 10 10 const dataAsFixture = (data: any): TestFixture => { 11 11 return data as TestFixture;
+13 -13
src/backend/tests/listenbrainz/listenbrainz.test.ts
··· 1 - import {describe, it} from 'mocha'; 2 - import {assert} from 'chai'; 1 + import { loggerTest } from "@foxxmd/logging"; 2 + import { assert } from 'chai'; 3 + import dayjs from "dayjs"; 4 + import { describe, it } from 'mocha'; 5 + import { http, HttpResponse } from "msw"; 6 + import { PlayObject } from "../../../core/Atomic.js"; 7 + import { UpstreamError } from "../../common/errors/UpstreamError.js"; 8 + 9 + import { ListenbrainzApiClient, ListenResponse } from "../../common/vendor/ListenbrainzApiClient.js"; 10 + import { ExpectedResults } from "../utils/interfaces.js"; 11 + import { withRequestInterception } from "../utils/networking.js"; 12 + import artistWithProperJoiner from './correctlyMapped/artistProperHasJoinerInName.json'; 3 13 // correct mappings 4 14 import multiArtistInArtistName from './correctlyMapped/multiArtistInArtistName.json'; 5 15 import multiArtistsInTrackName from './correctlyMapped/multiArtistInTrackName.json'; 6 - import noArtistMapping from './correctlyMapped/noArtistMapping.json'; 7 16 import multiMappedArtistsWithSingleUserArtist from './correctlyMapped/multiArtistMappingWithSingleRecordedArtist.json'; 8 - import artistWithProperJoiner from './correctlyMapped/artistProperHasJoinerInName.json'; 17 + import noArtistMapping from './correctlyMapped/noArtistMapping.json'; 9 18 import normalizedValues from './correctlyMapped/normalizedName.json'; 10 19 import slightlyDifferentNames from './correctlyMapped/trackNameSlightlyDifferent.json'; 11 20 12 21 // incorrect mappings 13 22 import incorrectMultiArtistsTrackName from './incorrectlyMapped/multiArtistsInTrackName.json'; 14 23 import veryWrong from './incorrectlyMapped/veryWrong.json'; 15 - 16 - import { ListenbrainzApiClient, ListenResponse } from "../../common/vendor/ListenbrainzApiClient.js"; 17 - import { PlayObject } from "../../../core/Atomic.js"; 18 - import dayjs from "dayjs"; 19 - import { withRequestInterception } from "../utils/networking.js"; 20 - import {http, HttpResponse} from "msw"; 21 - import { UpstreamError } from "../../common/errors/UpstreamError.js"; 22 - import { ExpectedResults } from "../utils/interfaces.js"; 23 - import {loggerTest} from "@foxxmd/logging"; 24 24 25 25 interface LZTestFixture { 26 26 data: ListenResponse
+13 -8
src/backend/tests/player/player.test.ts
··· 1 - import {describe, it} from 'mocha'; 2 - import {assert} from 'chai'; 3 - import { generatePlay } from "../utils/PlayTestUtils.js"; 4 - import { GenericPlayerState } from "../../sources/PlayerState/GenericPlayerState.js"; 5 - import { CALCULATED_PLAYER_STATUSES, NO_DEVICE, NO_USER, REPORTED_PLAYER_STATUSES } from "../../common/infrastructure/Atomic.js"; 6 - import { playObjDataMatch } from "../../utils.js"; 7 - import dayjs from "dayjs"; 1 + import { loggerTest } from "@foxxmd/logging"; 2 + import { assert } from 'chai'; 8 3 import clone from "clone"; 9 - import {loggerTest} from "@foxxmd/logging"; 4 + import dayjs from "dayjs"; 5 + import { describe, it } from 'mocha'; 6 + import { 7 + CALCULATED_PLAYER_STATUSES, 8 + NO_DEVICE, 9 + NO_USER, 10 + REPORTED_PLAYER_STATUSES 11 + } from "../../common/infrastructure/Atomic.js"; 12 + import { GenericPlayerState } from "../../sources/PlayerState/GenericPlayerState.js"; 13 + import { playObjDataMatch } from "../../utils.js"; 14 + import { generatePlay } from "../utils/PlayTestUtils.js"; 10 15 11 16 const logger = loggerTest; 12 17
+4 -4
src/backend/tests/scrobbler/TestScrobbler.ts
··· 1 - import AbstractScrobbleClient from "../../scrobblers/AbstractScrobbleClient.js"; 2 - import { PlayObject } from "../../../core/Atomic.js"; 3 - import { Notifiers } from "../../notifier/Notifiers.js"; 1 + import { loggerTest } from "@foxxmd/logging"; 4 2 import EventEmitter from "events"; 5 3 import request from "superagent"; 6 - import {loggerTest} from "@foxxmd/logging"; 4 + import { PlayObject } from "../../../core/Atomic.js"; 5 + import { Notifiers } from "../../notifier/Notifiers.js"; 6 + import AbstractScrobbleClient from "../../scrobblers/AbstractScrobbleClient.js"; 7 7 8 8 export class TestScrobbler extends AbstractScrobbleClient { 9 9
+11 -12
src/backend/tests/scrobbler/scrobblers.test.ts
··· 1 - import {describe, it, after, before} from 'mocha'; 2 - import chai, {assert} from 'chai'; 1 + import chai, { assert } from 'chai'; 3 2 import asPromised from 'chai-as-promised'; 4 3 import clone from 'clone'; 5 - import pEvent from 'p-event'; 4 + import dayjs from "dayjs"; 5 + import { after, before, describe, it } from 'mocha'; 6 6 import { http, HttpResponse } from 'msw'; 7 - 8 - chai.use(asPromised); 9 - 10 - import withDuration from '../plays/withDuration.json'; 7 + import pEvent from 'p-event'; 8 + import { PlayObject } from "../../../core/Atomic.js"; 9 + import { sleep } from "../../utils.js"; 11 10 import mixedDuration from '../plays/mixedDuration.json'; 11 + import withDuration from '../plays/withDuration.json'; 12 + import { MockNetworkError, withRequestInterception } from "../utils/networking.js"; 13 + import { asPlays, generatePlay, normalizePlays } from "../utils/PlayTestUtils.js"; 12 14 13 15 import { TestScrobbler } from "./TestScrobbler.js"; 14 - import { asPlays, generatePlay, normalizePlays } from "../utils/PlayTestUtils.js"; 15 - import dayjs from "dayjs"; 16 - import { sleep } from "../../utils.js"; 17 - import { MockNetworkError, withRequestInterception } from "../utils/networking.js"; 18 - import { PlayObject } from "../../../core/Atomic.js"; 16 + 17 + chai.use(asPromised); 19 18 20 19 const firstPlayDate = dayjs().subtract(1, 'hour'); 21 20 const olderFirstPlayDate = dayjs().subtract(4, 'hour');
+5 -5
src/backend/tests/utils/PlayTestUtils.ts
··· 1 - import { JsonPlayObject, ObjectPlayData, PlayData, PlayMeta, PlayObject } from "../../../core/Atomic.js"; 2 - import dayjs, {Dayjs} from "dayjs"; 3 - import utc from "dayjs/plugin/utc.js"; 1 + import { faker } from '@faker-js/faker'; 2 + import dayjs, { Dayjs } from "dayjs"; 3 + import duration from "dayjs/plugin/duration.js"; 4 4 import isBetween from "dayjs/plugin/isBetween.js"; 5 5 import relativeTime from "dayjs/plugin/relativeTime.js"; 6 - import duration from "dayjs/plugin/duration.js"; 7 6 import timezone from "dayjs/plugin/timezone.js"; 8 - import {faker} from '@faker-js/faker'; 7 + import utc from "dayjs/plugin/utc.js"; 8 + import { JsonPlayObject, ObjectPlayData, PlayMeta, PlayObject } from "../../../core/Atomic.js"; 9 9 10 10 dayjs.extend(utc) 11 11 dayjs.extend(isBetween);
+1 -1
src/backend/tests/utils/networking.ts
··· 1 + import { setupServer, SetupServer } from 'msw/node'; 1 2 import { NodeNetworkErrorCode, NodeNetworkException } from "../../common/errors/NodeErrors.js"; 2 - import { setupServer, SetupServer } from 'msw/node'; 3 3 4 4 export class MockNetworkError extends Error implements NodeNetworkException { 5 5
+4 -4
src/backend/tests/utils/strings.test.ts
··· 1 - import {describe, it} from 'mocha'; 2 - import {assert} from 'chai'; 1 + import { assert } from 'chai'; 2 + import { describe, it } from 'mocha'; 3 + import { intersect } from "../../utils.js"; 3 4 import { 4 5 compareNormalizedStrings, 5 6 normalizeStr, 6 7 parseTrackCredits, 7 8 uniqueNormalizedStrArr 8 9 } from "../../utils/StringUtils.js"; 9 - import testData from './playTestData.json'; 10 10 import { ExpectedResults } from "./interfaces.js"; 11 - import { intersect } from "../../utils.js"; 11 + import testData from './playTestData.json'; 12 12 13 13 interface PlayTestFixture { 14 14 caseHints: string[]
+4 -4
src/backend/common/infrastructure/config/aioConfig.ts
··· 1 - import { CommonSourceOptions, ScrobbleThresholds, SourceRetryOptions } from "./source/index.js"; 2 - import { RequestRetryOptions } from "./common.js"; 3 - import { SourceAIOConfig } from "./source/sources.js"; 1 + import { LogOptions } from "@foxxmd/logging"; 4 2 import { ClientAIOConfig } from "./client/clients.js"; 3 + import { RequestRetryOptions } from "./common.js"; 5 4 import { WebhookConfig } from "./health/webhooks.js"; 6 - import {LogOptions} from "@foxxmd/logging"; 5 + import { CommonSourceOptions, ScrobbleThresholds, SourceRetryOptions } from "./source/index.js"; 6 + import { SourceAIOConfig } from "./source/sources.js"; 7 7 8 8 9 9 export interface SourceDefaults extends SourceRetryOptions {
+2 -1
src/backend/common/infrastructure/typings/passport-deezer.d.ts
··· 1 1 2 2 declare module 'passport-deezer' { 3 3 //import {Strategy as Oauth2Strategy} from 'passport-oauth2'; 4 - import {Strategy as PassportStrategy} from "passport"; 4 + import { Strategy as PassportStrategy } from "passport"; 5 + 5 6 export class Strategy extends PassportStrategy { 6 7 constructor(options: any, verify: any); 7 8 }
+3 -3
src/backend/common/vendor/chromecast/ChromecastClientUtils.ts
··· 1 - import { REPORTED_PLAYER_STATUSES, ReportedPlayerStatus } from "../../infrastructure/Atomic.js"; 2 - import { PlatformApplication, PlatformType } from "./interfaces.js"; 3 - import {Media, MediaController, Result} from "@foxxmd/chromecast-client"; 1 + import { Media, MediaController, Result } from "@foxxmd/chromecast-client"; 4 2 import objectHash from "object-hash"; 5 3 import { PlayObject } from "../../../../core/Atomic.js"; 4 + import { REPORTED_PLAYER_STATUSES, ReportedPlayerStatus } from "../../infrastructure/Atomic.js"; 5 + import { PlatformApplication, PlatformType } from "./interfaces.js"; 6 6 7 7 export const chromePlayerStateToReported = (state: string): ReportedPlayerStatus => { 8 8 switch (state) {
+3 -3
src/backend/common/vendor/chromecast/interfaces.ts
··· 1 - import {createPlatform, MediaController, PersistentClient} from "@foxxmd/chromecast-client"; 1 + import { createPlatform, MediaController } from "@foxxmd/chromecast-client"; 2 + import { Logger } from "@foxxmd/logging"; 3 + import { Dayjs } from "dayjs"; 2 4 import { FormatPlayObjectOptions } from "../../infrastructure/Atomic.js"; 3 - import {Dayjs} from "dayjs"; 4 - import {Logger} from "@foxxmd/logging"; 5 5 6 6 export type PlatformType = ReturnType<typeof createPlatform>; 7 7 export interface PlatformApplication {
+1 -1
src/backend/common/vendor/maloja/interfaces.ts
··· 1 - import {ResponseError} from "superagent"; 1 + import { ResponseError } from "superagent"; 2 2 import { findCauseByFunc } from "../../../utils.js"; 3 3 import { isSuperAgentResponseError } from "../../errors/ErrorUtils.js"; 4 4
+1 -1
src/backend/common/vendor/subsonic/interfaces.ts
··· 1 - import {Response} from 'superagent'; 1 + import { Response } from 'superagent'; 2 2 3 3 export interface SubsonicResponseCommon { 4 4 status: 'failed' | 'ok'
+1 -1
src/backend/common/infrastructure/config/client/clients.ts
··· 1 - import { MalojaClientAIOConfig, MalojaClientConfig } from "./maloja.js"; 2 1 import { LastfmClientAIOConfig, LastfmClientConfig } from "./lastfm.js"; 3 2 import { ListenBrainzClientAIOConfig, ListenBrainzClientConfig } from "./listenbrainz.js"; 3 + import { MalojaClientAIOConfig, MalojaClientConfig } from "./maloja.js"; 4 4 5 5 export type ClientConfig = MalojaClientConfig | LastfmClientConfig | ListenBrainzClientConfig; 6 6
+1 -1
src/backend/common/infrastructure/config/client/lastfm.ts
··· 1 - import { CommonClientConfig, CommonClientData } from "./index.js"; 2 1 import { RequestRetryOptions } from "../common.js"; 2 + import { CommonClientConfig, CommonClientData } from "./index.js"; 3 3 4 4 export interface LastfmData extends RequestRetryOptions { 5 5 /**
+1 -1
src/backend/common/infrastructure/config/client/listenbrainz.ts
··· 1 - import { CommonClientConfig, CommonClientData } from "./index.js"; 2 1 import { RequestRetryOptions } from "../common.js"; 2 + import { CommonClientConfig, CommonClientData } from "./index.js"; 3 3 4 4 export interface ListenBrainzData extends RequestRetryOptions{ 5 5 /**
+1 -1
src/backend/common/infrastructure/config/client/maloja.ts
··· 1 - import { CommonClientConfig, CommonClientData } from "./index.js"; 2 1 import { RequestRetryOptions } from "../common.js"; 2 + import { CommonClientConfig, CommonClientData } from "./index.js"; 3 3 4 4 export interface MalojaClientData extends RequestRetryOptions, CommonClientData { 5 5 /**
+1 -1
src/backend/common/infrastructure/config/source/jriver.ts
··· 1 - import { CommonSourceConfig, CommonSourceData } from "./index.js"; 2 1 import { PollingOptions } from "../common.js"; 2 + import { CommonSourceConfig, CommonSourceData } from "./index.js"; 3 3 4 4 export interface JRiverData extends CommonSourceData, PollingOptions { 5 5 /**
+1 -1
src/backend/common/infrastructure/config/source/kodi.ts
··· 1 - import { CommonSourceConfig, CommonSourceData } from "./index.js"; 2 1 import { PollingOptions } from "../common.js"; 2 + import { CommonSourceConfig, CommonSourceData } from "./index.js"; 3 3 4 4 5 5 export interface KodiData extends CommonSourceData, PollingOptions {
+1 -1
src/backend/common/infrastructure/config/source/lastfm.ts
··· 1 1 import { LastfmData } from "../client/lastfm.js"; 2 - import { CommonSourceConfig, CommonSourceData } from "./index.js"; 3 2 import { PollingOptions } from "../common.js"; 3 + import { CommonSourceConfig, CommonSourceData } from "./index.js"; 4 4 5 5 export interface LastFmSourceData extends CommonSourceData, PollingOptions, LastfmData{} 6 6
+1 -1
src/backend/common/infrastructure/config/source/listenbrainz.ts
··· 1 - import { CommonSourceConfig, CommonSourceData } from "./index.js"; 2 1 import { ListenBrainzData } from "../client/listenbrainz.js"; 3 2 import { PollingOptions } from "../common.js"; 3 + import { CommonSourceConfig, CommonSourceData } from "./index.js"; 4 4 5 5 export interface ListenBrainzSourceData extends ListenBrainzData, CommonSourceData, PollingOptions { 6 6 }
+1 -1
src/backend/common/infrastructure/config/source/mopidy.ts
··· 1 - import { CommonSourceConfig, CommonSourceData } from "./index.js"; 2 1 import { PollingOptions } from "../common.js"; 2 + import { CommonSourceConfig, CommonSourceData } from "./index.js"; 3 3 4 4 export interface MopidyData extends CommonSourceData, PollingOptions { 5 5 /**
+10 -10
src/backend/common/infrastructure/config/source/sources.ts
··· 1 - import { SpotifySourceAIOConfig, SpotifySourceConfig } from "./spotify.js"; 2 - import { PlexSourceAIOConfig, PlexSourceConfig } from "./plex.js"; 3 - import { TautulliSourceAIOConfig, TautulliSourceConfig } from "./tautulli.js"; 1 + import { ChromecastSourceAIOConfig, ChromecastSourceConfig } from "./chromecast.js"; 4 2 import { DeezerSourceAIOConfig, DeezerSourceConfig } from "./deezer.js"; 5 - import { SubsonicSourceAIOConfig, SubSonicSourceConfig } from "./subsonic.js"; 6 3 import { JellySourceAIOConfig, JellySourceConfig } from "./jellyfin.js"; 7 - import { LastFmSouceAIOConfig, LastfmSourceConfig } from "./lastfm.js"; 8 - import { YTMusicSourceAIOConfig, YTMusicSourceConfig } from "./ytmusic.js"; 9 - import { MPRISSourceAIOConfig, MPRISSourceConfig } from "./mpris.js"; 10 - import { MopidySourceAIOConfig, MopidySourceConfig } from "./mopidy.js"; 11 - import { ListenBrainzSourceAIOConfig, ListenBrainzSourceConfig } from "./listenbrainz.js"; 12 4 import { JRiverSourceAIOConfig, JRiverSourceConfig } from "./jriver.js"; 13 5 import { KodiSourceAIOConfig, KodiSourceConfig } from "./kodi.js"; 6 + import { LastFmSouceAIOConfig, LastfmSourceConfig } from "./lastfm.js"; 7 + import { ListenBrainzSourceAIOConfig, ListenBrainzSourceConfig } from "./listenbrainz.js"; 8 + import { MopidySourceAIOConfig, MopidySourceConfig } from "./mopidy.js"; 9 + import { MPRISSourceAIOConfig, MPRISSourceConfig } from "./mpris.js"; 10 + import { PlexSourceAIOConfig, PlexSourceConfig } from "./plex.js"; 11 + import { SpotifySourceAIOConfig, SpotifySourceConfig } from "./spotify.js"; 12 + import { SubsonicSourceAIOConfig, SubSonicSourceConfig } from "./subsonic.js"; 13 + import { TautulliSourceAIOConfig, TautulliSourceConfig } from "./tautulli.js"; 14 14 import { WebScrobblerSourceAIOConfig, WebScrobblerSourceConfig } from "./webscrobbler.js"; 15 - import { ChromecastSourceConfig, ChromecastSourceAIOConfig } from "./chromecast.js"; 15 + import { YTMusicSourceAIOConfig, YTMusicSourceConfig } from "./ytmusic.js"; 16 16 17 17 18 18 export type SourceConfig = SpotifySourceConfig | PlexSourceConfig | TautulliSourceConfig | DeezerSourceConfig | SubSonicSourceConfig | JellySourceConfig | LastfmSourceConfig | YTMusicSourceConfig | MPRISSourceConfig | MopidySourceConfig | ListenBrainzSourceConfig | JRiverSourceConfig | KodiSourceConfig | WebScrobblerSourceConfig | ChromecastSourceConfig;
+1 -1
src/backend/common/infrastructure/config/source/spotify.ts
··· 1 - import { CommonSourceConfig, CommonSourceData } from "./index.js"; 2 1 import { PollingOptions } from "../common.js"; 2 + import { CommonSourceConfig, CommonSourceData } from "./index.js"; 3 3 4 4 export interface SpotifySourceData extends CommonSourceData, PollingOptions { 5 5 /**
+1 -1
src/backend/common/infrastructure/config/source/subsonic.ts
··· 1 - import { CommonSourceConfig, CommonSourceData } from "./index.js"; 2 1 import { PollingOptions } from "../common.js"; 2 + import { CommonSourceConfig, CommonSourceData } from "./index.js"; 3 3 4 4 export interface SubsonicData extends CommonSourceData, PollingOptions { 5 5 /**
+1 -1
src/backend/common/infrastructure/config/source/ytmusic.ts
··· 1 - import { CommonSourceConfig, CommonSourceData } from "./index.js"; 2 1 import { PollingOptions } from "../common.js"; 2 + import { CommonSourceConfig, CommonSourceData } from "./index.js"; 3 3 4 4 export interface YTMusicData extends CommonSourceData, PollingOptions { 5 5 /**