[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: Move transform typings to own file

FoxxMD (Nov 20, 2025, 3:11 PM UTC) 6a677b6f 5c418d56

+80 -73
+8 -7
src/backend/common/AbstractComponent.ts
··· 13 13 } from "../utils/PlayTransformUtils.js"; 14 14 import { hasNodeNetworkException } from "./errors/NodeErrors.js"; 15 15 import { hasUpstreamError } from "./errors/UpstreamError.js"; 16 - import { 17 - ConditionalSearchAndReplaceRegExp, 18 - PlayTransformParts, PlayTransformPartsArray, 19 - PlayTransformRules, 20 - TRANSFORM_HOOK, 21 - TransformHook 22 - } from "./infrastructure/Atomic.js"; 23 16 import { CommonClientConfig } from "./infrastructure/config/client/index.js"; 24 17 import { CommonSourceConfig } from "./infrastructure/config/source/index.js"; 25 18 import play = Simulate.play; 26 19 import { WebhookPayload } from "./infrastructure/config/health/webhooks.js"; 27 20 import { AuthCheckError, BuildDataError, ConnectionCheckError, ParseCacheError, PostInitError, TransformRulesError } from "./errors/MSErrors.js"; 28 21 import { messageWithCauses, messageWithCausesTruncatedDefault } from "../utils/ErrorUtils.js"; 22 + import { 23 + ConditionalSearchAndReplaceRegExp, 24 + PlayTransformParts, 25 + PlayTransformPartsArray, 26 + PlayTransformRules, 27 + TRANSFORM_HOOK, 28 + TransformHook 29 + } from "./infrastructure/Transform.js"; 29 30 30 31 export default abstract class AbstractComponent { 31 32 requiresAuth: boolean = false;
-56
src/backend/common/infrastructure/Atomic.ts
··· 1 1 import { Logger } from '@foxxmd/logging'; 2 - import { SearchAndReplaceRegExp } from "@foxxmd/regex-buddy-core"; 3 2 import { Dayjs } from "dayjs"; 4 3 import { Request, Response } from "express"; 5 4 import { NextFunction, ParamsDictionary, Query } from "express-serve-static-core"; ··· 298 297 export type AbstractApiOptions = Record<any, any> & { logger: Logger } 299 298 300 299 export type keyOmit<T, U extends keyof any> = T & { [P in U]?: never } 301 - 302 - export interface ConditionalSearchAndReplaceRegExp extends SearchAndReplaceRegExp{ 303 - when?: WhenConditionsConfig 304 - } 305 - 306 - export type ConditionalSearchAndReplaceTerm = Omit<ConditionalSearchAndReplaceRegExp, 'test'> 307 - 308 - export type SearchAndReplaceTerm = string | ConditionalSearchAndReplaceTerm; 309 - 310 - export type PlayTransformParts<T> = PlayTransformPartsAtomic<T[]> & { when?: WhenConditionsConfig }; 311 - 312 - export type PlayTransformPartsArray<T> = PlayTransformParts<T>[]; 313 - 314 - export type PlayTransformPartsConfig<T> = PlayTransformPartsArray<T> | PlayTransformParts<T>; 315 - 316 - export interface PlayTransformPartsAtomic<T> { 317 - title?: T 318 - artists?: T 319 - album?: T 320 - } 321 - 322 - export interface PlayTransformHooksConfig<T> { 323 - preCompare?: PlayTransformPartsConfig<T> 324 - compare?: { 325 - candidate?: PlayTransformPartsConfig<T> 326 - existing?: PlayTransformPartsConfig<T> 327 - } 328 - postCompare?: PlayTransformPartsConfig<T> 329 - } 330 - 331 - export interface PlayTransformHooks<T> extends PlayTransformHooksConfig<T> { 332 - preCompare?: PlayTransformPartsArray<T> 333 - compare?: { 334 - candidate?: PlayTransformPartsArray<T> 335 - existing?: PlayTransformPartsArray<T> 336 - } 337 - postCompare?: PlayTransformPartsArray<T> 338 - } 339 - 340 - export type PlayTransformRules = PlayTransformHooks<ConditionalSearchAndReplaceRegExp> 341 - 342 - export type TransformHook = 'preCompare' | 'compare' | 'candidate' | 'existing' | 'postCompare'; 343 - export const TRANSFORM_HOOK = { 344 - preCompare: 'preCompare' as TransformHook, 345 - candidate: 'candidate' as TransformHook, 346 - existing: 'existing' as TransformHook, 347 - postCompare: 'postCompare' as TransformHook, 348 - } 349 - export type PlayTransformConfig = PlayTransformHooksConfig<SearchAndReplaceTerm>; 350 - export type PlayTransformOptions = PlayTransformConfig & { log?: boolean | 'all' } 351 - 352 - export type WhenParts<T> = PlayTransformPartsAtomic<T>; 353 - 354 - export type WhenConditions<T> = WhenParts<T>[]; 355 - export type WhenConditionsConfig = WhenConditions<string>; 356 300 357 301 export type WithRequiredProperty<Type, Key extends keyof Type> = Type & { 358 302 [Property in Key]-?: Type[Property];
+54
src/backend/common/infrastructure/Transform.ts
··· 1 + import { SearchAndReplaceRegExp } from "@foxxmd/regex-buddy-core"; 2 + 3 + export interface ConditionalSearchAndReplaceRegExp extends SearchAndReplaceRegExp { 4 + when?: WhenConditionsConfig 5 + } 6 + 7 + export type ConditionalSearchAndReplaceTerm = Omit<ConditionalSearchAndReplaceRegExp, 'test'> 8 + export type SearchAndReplaceTerm = string | ConditionalSearchAndReplaceTerm; 9 + export type ExternalMetadataTerm = true | undefined; 10 + 11 + export type PlayTransformParts<T> = PlayTransformPartsAtomic<T[]> & { when?: WhenConditionsConfig }; 12 + export type PlayTransformPartsArray<T> = PlayTransformParts<T>[]; 13 + export type PlayTransformPartsConfig<T> = PlayTransformPartsArray<T> | PlayTransformParts<T>; 14 + 15 + export interface PlayTransformPartsAtomic<T> { 16 + title?: T 17 + artists?: T 18 + album?: T 19 + } 20 + 21 + export interface PlayTransformHooksConfig<T> { 22 + preCompare?: PlayTransformPartsConfig<T> 23 + compare?: { 24 + candidate?: PlayTransformPartsConfig<T> 25 + existing?: PlayTransformPartsConfig<T> 26 + } 27 + postCompare?: PlayTransformPartsConfig<T> 28 + } 29 + 30 + export interface PlayTransformHooks<T> extends PlayTransformHooksConfig<T> { 31 + preCompare?: PlayTransformPartsArray<T> 32 + compare?: { 33 + candidate?: PlayTransformPartsArray<T> 34 + existing?: PlayTransformPartsArray<T> 35 + } 36 + postCompare?: PlayTransformPartsArray<T> 37 + } 38 + 39 + export type PlayTransformRules = PlayTransformHooks<ConditionalSearchAndReplaceRegExp> 40 + export type TransformHook = 'preCompare' | 'compare' | 'candidate' | 'existing' | 'postCompare'; 41 + export const TRANSFORM_HOOK = { 42 + preCompare: 'preCompare' as TransformHook, 43 + candidate: 'candidate' as TransformHook, 44 + existing: 'existing' as TransformHook, 45 + postCompare: 'postCompare' as TransformHook, 46 + } 47 + export type PlayTransformConfig = PlayTransformHooksConfig<SearchAndReplaceTerm>; 48 + export type PlayTransformOptions = PlayTransformConfig & { log?: boolean | 'all' } 49 + export type WhenParts<T> = PlayTransformPartsAtomic<T>; 50 + export type WhenConditions<T> = WhenParts<T>[]; 51 + export type WhenConditionsConfig = WhenConditions<string>; 52 + 53 + 54 + //export type PlayTransform
+1 -1
src/backend/common/infrastructure/config/client/index.ts
··· 1 - import { PlayTransformConfig, PlayTransformOptions } from "../../Atomic.js"; 1 + import { PlayTransformConfig, PlayTransformOptions } from "../../Transform.js"; 2 2 import { CommonConfig, CommonData, RequestRetryOptions } from "../common.js"; 3 3 4 4 /**
+2 -1
src/backend/common/infrastructure/config/source/index.ts
··· 1 1 import { FileLogOptions, LogLevel } from "@foxxmd/logging"; 2 - import { PlayTransformConfig, PlayTransformOptions } from "../../Atomic.js"; 2 + 3 + import { PlayTransformConfig, PlayTransformOptions } from "../../Transform.js"; 3 4 import { CommonConfig, CommonData, RequestRetryOptions } from "../common.js"; 4 5 5 6 export interface SourceRetryOptions extends RequestRetryOptions {
+2 -1
src/backend/scrobblers/AbstractScrobbleClient.ts
··· 25 25 ScrobbledPlayObject, 26 26 SourceIdentifier, 27 27 TIME_WEIGHT, 28 - TITLE_WEIGHT, TRANSFORM_HOOK, 28 + TITLE_WEIGHT, 29 29 } from "../common/infrastructure/Atomic.js"; 30 30 import { CommonClientConfig, NowPlayingOptions, UpstreamRefreshOptions } from "../common/infrastructure/config/client/index.js"; 31 + import { TRANSFORM_HOOK } from "../common/infrastructure/Transform.js"; 31 32 import { Notifiers } from "../notifier/Notifiers.js"; 32 33 import { 33 34 comparingMultipleArtists,
+2 -1
src/backend/sources/AbstractSource.ts
··· 18 18 PlayUserId, 19 19 ProgressAwarePlayObject, 20 20 SINGLE_USER_PLATFORM_ID, 21 - SourceType, TRANSFORM_HOOK, 21 + SourceType, 22 22 } from "../common/infrastructure/Atomic.js"; 23 23 import { SourceConfig } from "../common/infrastructure/config/source/sources.js"; 24 + import { TRANSFORM_HOOK } from "../common/infrastructure/Transform.js"; 24 25 import TupleMap from "../common/TupleMap.js"; 25 26 import { 26 27 difference,
+3 -2
src/backend/sources/DeezerInternalSource.ts
··· 2 2 import EventEmitter from "events"; 3 3 import request, { Request, Response, SuperAgent } from 'superagent'; 4 4 import { PlayObject, SOURCE_SOT, TA_CLOSE, TA_DURING, TA_EXACT, TA_FUZZY, TemporalAccuracy } from "../../core/Atomic.js"; 5 - import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig, TRANSFORM_HOOK } from "../common/infrastructure/Atomic.js"; 5 + import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 6 6 import { DeezerInternalSourceConfig, DeezerInternalTrackData, DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; 7 + import { TRANSFORM_HOOK } from "../common/infrastructure/Transform.js"; 7 8 import { parseRetryAfterSecsFromObj, playObjDataMatch, readJson, sleep, sortByOldestPlayDate, writeFile, } from "../utils.js"; 8 9 import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; 9 10 import { CookieJar, Cookie } from 'tough-cookie'; ··· 278 279 const u = new URL(`https://www.deezer.com/ajax/gw-light.php?${params.toString()}`); 279 280 280 281 return u; 281 - } 282 + }
+2 -1
src/backend/tests/component/component.test.ts
··· 3 3 import asPromised from 'chai-as-promised'; 4 4 import { after, before, describe, it } from 'mocha'; 5 5 import AbstractComponent from "../../common/AbstractComponent.js"; 6 - import { TRANSFORM_HOOK } from "../../common/infrastructure/Atomic.js"; 6 + 7 + import { TRANSFORM_HOOK } from "../../common/infrastructure/Transform.js"; 7 8 8 9 import { isConditionalSearchAndReplace } from "../../utils/PlayTransformUtils.js"; 9 10 import { asPlays, generatePlay, normalizePlays } from "../utils/PlayTestUtils.js";
+6 -3
src/backend/utils/PlayTransformUtils.ts
··· 2 2 import { searchAndReplace as searchAndReplaceFunc, testMaybeRegex as testMaybeRegexFunc } from "@foxxmd/regex-buddy-core"; 3 3 import { ObjectPlayData, PlayObject } from "../../core/Atomic.js"; 4 4 import { buildTrackString } from "../../core/StringUtils.js"; 5 + 5 6 import { 6 7 ConditionalSearchAndReplaceRegExp, 7 - PlayTransformParts, PlayTransformPartsArray, PlayTransformPartsConfig, PlayTransformRules, 8 - SearchAndReplaceTerm, 8 + PlayTransformParts, 9 + PlayTransformPartsArray, 10 + PlayTransformPartsConfig, 11 + PlayTransformRules, SearchAndReplaceTerm, 9 12 WhenConditionsConfig, 10 13 WhenParts 11 - } from "../common/infrastructure/Atomic.js"; 14 + } from "../common/infrastructure/Transform.js"; 12 15 13 16 export const isWhenCondition = (val: unknown): val is WhenParts<string> => { 14 17 if (val !== null && typeof val === 'object') {