[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.

reactor: try dynamic import for components

FoxxMD (Jan 21, 2026, 4:51 PM UTC) 50e61374 69d76d2d

+71 -37
+15 -9
src/backend/scrobblers/ScrobbleClients.ts
··· 14 14 import { readJson } from '../utils/DataUtils.js'; 15 15 import { validateJson } from "../utils/ValidationUtils.js"; 16 16 import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; 17 - import LastfmScrobbler from "./LastfmScrobbler.js"; 18 - import ListenbrainzScrobbler from "./ListenbrainzScrobbler.js"; 19 - import MalojaScrobbler from "./MalojaScrobbler.js"; 20 - import KoitoScrobbler from './KoitoScrobbler.js'; 17 + //import LastfmScrobbler from "./LastfmScrobbler.js"; 18 + //import ListenbrainzScrobbler from "./ListenbrainzScrobbler.js"; 19 + //import MalojaScrobbler from "./MalojaScrobbler.js"; 20 + //import KoitoScrobbler from './KoitoScrobbler.js'; 21 21 import { KoitoClientConfig } from '../common/infrastructure/config/client/koito.js'; 22 - import TealScrobbler from './TealfmScrobbler.js'; 22 + //import TealScrobbler from './TealfmScrobbler.js'; 23 23 import { TealClientConfig } from '../common/infrastructure/config/client/tealfm.js'; 24 - import RockskyScrobbler from './RockskyScrobbler.js'; 24 + //import RockskyScrobbler from './RockskyScrobbler.js'; 25 25 import { RockSkyClientConfig } from '../common/infrastructure/config/client/rocksky.js'; 26 26 import { CommonClientOptions } from '../common/infrastructure/config/client/index.js'; 27 27 import { ExternalMetadataTerm, PlayTransformHooks } from '../common/infrastructure/Transform.js'; 28 - import LibrefmScrobbler from './LibrefmScrobbler.js'; 28 + //import LibrefmScrobbler from './LibrefmScrobbler.js'; 29 29 import { LibrefmClientConfig } from '../common/infrastructure/config/client/librefm.js'; 30 30 31 31 type groupedNamedConfigs = {[key: string]: ParsedConfig[]}; ··· 34 34 35 35 export default class ScrobbleClients { 36 36 37 - /** @type AbstractScrobbleClient[] */ 38 - clients: (MalojaScrobbler | LastfmScrobbler | KoitoScrobbler | TealScrobbler)[] = []; 37 + clients: AbstractScrobbleClient[] = []; 39 38 logger: Logger; 40 39 41 40 internalConfig: InternalConfig; ··· 410 409 this.logger.debug({labels: [`${type} - ${name}`]}, `Constructing Client from ${source}`); 411 410 switch (type) { 412 411 case 'maloja': 412 + const MalojaScrobbler = (await import('./MalojaScrobbler.js')).default; 413 413 newClient = new MalojaScrobbler(name, ({...clientConfig, data} as unknown as MalojaClientConfig), notifier, this.emitter, this.logger); 414 414 break; 415 415 case 'lastfm': 416 + const LastfmScrobbler = (await import('./LastfmScrobbler.js')).default; 416 417 newClient = new LastfmScrobbler(name, {...clientConfig, data } as unknown as LastfmClientConfig, this.internalConfig, notifier, this.emitter, this.logger); 417 418 break; 418 419 case 'librefm': 420 + const LibrefmScrobbler = (await import('./LibrefmScrobbler.js')).default; 419 421 newClient = new LibrefmScrobbler(name, {...clientConfig, data } as unknown as LibrefmClientConfig, this.internalConfig, notifier, this.emitter, this.logger); 420 422 break; 421 423 case 'listenbrainz': 424 + const ListenbrainzScrobbler = (await import('./ListenbrainzScrobbler.js')).default; 422 425 newClient = new ListenbrainzScrobbler(name, {...clientConfig, data: {configDir: this.internalConfig.configDir, ...data} } as unknown as ListenBrainzClientConfig, {}, notifier, this.emitter, this.logger); 423 426 break; 424 427 case 'koito': 428 + const KoitoScrobbler = (await import('./KoitoScrobbler.js')).default; 425 429 newClient = new KoitoScrobbler(name, {...clientConfig, data: {configDir: this.internalConfig.configDir, ...data} } as unknown as KoitoClientConfig, {}, notifier, this.emitter, this.logger); 426 430 break; 427 431 case 'tealfm': 432 + const TealScrobbler = (await import('./TealfmScrobbler.js')).default; 428 433 newClient = new TealScrobbler(name, {...clientConfig, data: {...data}} as unknown as TealClientConfig, {}, notifier, this.emitter, this.logger); 429 434 break; 430 435 case 'rocksky': 436 + const RockskyScrobbler = (await import('./RockskyScrobbler.js')).default; 431 437 newClient = new RockskyScrobbler(name, {...clientConfig, data: {configDir: this.internalConfig.configDir, ...data} } as unknown as RockSkyClientConfig, {}, notifier, this.emitter, this.logger); 432 438 break; 433 439 default:
+56 -28
src/backend/sources/ScrobbleSources.ts
··· 35 35 import { readJson } from '../utils/DataUtils.js'; 36 36 import { validateJson } from "../utils/ValidationUtils.js"; 37 37 import AbstractSource from "./AbstractSource.js"; 38 - import { AzuracastSource } from "./AzuracastSource.js"; 39 - import { ChromecastSource } from "./ChromecastSource.js"; 40 - import DeezerSource from "./DeezerSource.js"; 41 - import { EndpointListenbrainzSource } from "./EndpointListenbrainzSource.js"; 42 - import { EndpointLastfmSource } from "./EndpointLastfmSource.js"; 43 - import JellyfinApiSource from "./JellyfinApiSource.js"; 44 - import { JRiverSource } from "./JRiverSource.js"; 45 - import { KodiSource } from "./KodiSource.js"; 46 - import LastfmSource from "./LastfmSource.js"; 47 - import ListenbrainzSource from "./ListenbrainzSource.js"; 48 - import { MopidySource } from "./MopidySource.js"; 49 - import { MPDSource } from "./MPDSource.js"; 50 - import { MPRISSource } from "./MPRISSource.js"; 51 - import { MusikcubeSource } from "./MusikcubeSource.js"; 52 - import { MusicCastSource } from "./MusicCastSource.js"; 53 - import SpotifySource from "./SpotifySource.js"; 54 - import { SubsonicSource } from "./SubsonicSource.js"; 55 - import { VLCSource } from "./VLCSource.js"; 56 - import { WebScrobblerSource } from "./WebScrobblerSource.js"; 57 - import YTMusicSource from "./YTMusicSource.js"; 58 - import PlexApiSource from './PlexApiSource.js'; 38 + //import { AzuracastSource } from "./AzuracastSource.js"; 39 + //import { ChromecastSource } from "./ChromecastSource.js"; 40 + //import DeezerSource from "./DeezerSource.js"; 41 + //import { EndpointListenbrainzSource } from "./EndpointListenbrainzSource.js"; 42 + //import { EndpointLastfmSource } from "./EndpointLastfmSource.js"; 43 + //import JellyfinApiSource from "./JellyfinApiSource.js"; 44 + //import { JRiverSource } from "./JRiverSource.js"; 45 + //import { KodiSource } from "./KodiSource.js"; 46 + //import LastfmSource from "./LastfmSource.js"; 47 + //import ListenbrainzSource from "./ListenbrainzSource.js"; 48 + //import { MopidySource } from "./MopidySource.js"; 49 + //import { MPDSource } from "./MPDSource.js"; 50 + //import { MPRISSource } from "./MPRISSource.js"; 51 + //import { MusikcubeSource } from "./MusikcubeSource.js"; 52 + //import { MusicCastSource } from "./MusicCastSource.js"; 53 + //import SpotifySource from "./SpotifySource.js"; 54 + //import { SubsonicSource } from "./SubsonicSource.js"; 55 + //import { VLCSource } from "./VLCSource.js"; 56 + //import { WebScrobblerSource } from "./WebScrobblerSource.js"; 57 + //import YTMusicSource from "./YTMusicSource.js"; 58 + //import PlexApiSource from './PlexApiSource.js'; 59 59 import { nonEmptyStringOrDefault } from '../../core/StringUtils.js'; 60 - import { IcecastSource } from './IcecastSource.js'; 61 - import DeezerInternalSource from './DeezerInternalSource.js'; 62 - import KoitoSource from './KoitoSource.js'; 60 + //import { IcecastSource } from './IcecastSource.js'; 61 + //import DeezerInternalSource from './DeezerInternalSource.js'; 62 + //import KoitoSource from './KoitoSource.js'; 63 63 import { KoitoSourceConfig } from '../common/infrastructure/config/source/koito.js'; 64 - import MalojaSource from './MalojaSource.js'; 65 - import TealfmSource from './TealfmSource.js'; 64 + //import MalojaSource from './MalojaSource.js'; 65 + //import TealfmSource from './TealfmSource.js'; 66 66 import { TealSourceConfig } from '../common/infrastructure/config/source/tealfm.js'; 67 67 import { RockskySourceConfig } from '../common/infrastructure/config/source/rocksky.js'; 68 - import RockskySource from './RockskySource.js'; 68 + //import RockskySource from './RockskySource.js'; 69 69 import { CommonSourceOptions } from '../common/infrastructure/config/source/index.js'; 70 70 import { ExternalMetadataTerm, PlayTransformHooks, PlayTransformOptions } from '../common/infrastructure/Transform.js'; 71 - import LibrefmSource from './LibrefmSource.js'; 71 + //import LibrefmSource from './LibrefmSource.js'; 72 72 import { LibrefmSourceConfig } from '../common/infrastructure/config/source/librefm.js'; 73 73 74 74 type groupedNamedConfigs = {[key: string]: ParsedConfig[]}; ··· 907 907 let newSource: AbstractSource; 908 908 switch (type) { 909 909 case 'spotify': 910 + const SpotifySource = (await import('./SpotifySource.js')).default; 910 911 newSource = new SpotifySource(name, compositeConfig as SpotifySourceConfig, this.internalConfig, this.emitter); 911 912 break; 912 913 case 'plex': 914 + const PlexApiSource = (await import('./PlexApiSource.js')).default; 913 915 newSource = await new PlexApiSource(name, compositeConfig as PlexApiSourceConfig, this.internalConfig, this.emitter); 914 916 break; 915 917 case 'subsonic': 918 + const {SubsonicSource} = (await import('./SubsonicSource.js')); 916 919 newSource = new SubsonicSource(name, compositeConfig as SubSonicSourceConfig, this.internalConfig, this.emitter); 917 920 break; 918 921 case 'jellyfin': 922 + const JellyfinApiSource = (await import('./JellyfinApiSource.js')).default; 919 923 newSource = await new JellyfinApiSource(name, compositeConfig as JellyApiSourceConfig, this.internalConfig, this.emitter); 920 924 break; 921 925 case 'lastfm': 926 + const LastfmSource = (await import('./LastfmSource.js')).default; 922 927 newSource = await new LastfmSource(name, compositeConfig as LastfmSourceConfig, this.internalConfig, this.emitter); 923 928 break; 924 929 case 'librefm': 930 + const LibrefmSource = (await import('./LibrefmSource.js')).default; 925 931 newSource = await new LibrefmSource(name, compositeConfig as LibrefmSourceConfig, this.internalConfig, this.emitter); 926 932 break; 927 933 case 'deezer': 928 934 const deezerConfig = compositeConfig as DeezerCompatConfig; 929 935 if(deezerConfig.data.arl !== undefined) { 936 + const DeezerInternalSource = (await import('./DeezerInternalSource.js')).default; 930 937 newSource = await new DeezerInternalSource(name, compositeConfig as DeezerInternalSourceConfig, this.internalConfig, this.emitter); 931 938 } else { 939 + const DeezerSource = (await import('./DeezerSource.js')).default; 932 940 newSource = await new DeezerSource(name, compositeConfig as DeezerSourceConfig, this.internalConfig, this.emitter); 933 941 } 934 942 break; 935 943 case 'ytmusic': 944 + const YTMusicSource = (await import('./YTMusicSource.js')).default; 936 945 newSource = await new YTMusicSource(name, compositeConfig as YTMusicSourceConfig, this.internalConfig, this.emitter); 937 946 break; 938 947 case 'mpris': 948 + const {MPRISSource} = (await import('./MPRISSource.js')); 939 949 newSource = await new MPRISSource(name, compositeConfig as MPRISSourceConfig, this.internalConfig, this.emitter); 940 950 break; 941 951 case 'mopidy': 952 + const {MopidySource} = (await import('./MopidySource.js')); 942 953 newSource = await new MopidySource(name, compositeConfig as MopidySourceConfig, this.internalConfig, this.emitter); 943 954 break; 944 955 case 'listenbrainz': 956 + const ListenbrainzSource = (await import('./ListenbrainzSource.js')).default; 945 957 newSource = await new ListenbrainzSource(name, compositeConfig as ListenBrainzSourceConfig, this.internalConfig, this.emitter); 946 958 break; 947 959 case 'endpointlz': 960 + const {EndpointListenbrainzSource} = (await import('./EndpointListenbrainzSource.js')); 948 961 newSource = await new EndpointListenbrainzSource(name, compositeConfig as ListenbrainzEndpointSourceConfig, this.internalConfig, this.emitter); 949 962 break; 950 963 case 'endpointlfm': 964 + const {EndpointLastfmSource} = (await import('./EndpointLastfmSource.js')); 951 965 newSource = await new EndpointLastfmSource(name, compositeConfig as LastFMEndpointSourceConfig, this.internalConfig, this.emitter); 952 966 break; 953 967 case 'icecast': 968 + const {IcecastSource} = (await import('./IcecastSource.js')); 954 969 newSource = await new IcecastSource(name, compositeConfig as IcecastSourceConfig, this.internalConfig, this.emitter); 955 970 break; 956 971 case 'jriver': 972 + const {JRiverSource} = (await import('./JRiverSource.js')); 957 973 newSource = await new JRiverSource(name, compositeConfig as JRiverSourceConfig, this.internalConfig, this.emitter); 958 974 break; 959 975 case 'kodi': 976 + const {KodiSource} = (await import('./KodiSource.js')); 960 977 newSource = await new KodiSource(name, compositeConfig as KodiSourceConfig, this.internalConfig, this.emitter); 961 978 break; 962 979 case 'webscrobbler': 980 + const {WebScrobblerSource} = (await import('./WebScrobblerSource.js')); 963 981 newSource = await new WebScrobblerSource(name, compositeConfig as WebScrobblerSourceConfig, this.internalConfig, this.emitter); 964 982 break; 965 983 case 'chromecast': 984 + const {ChromecastSource} = (await import('./ChromecastSource.js')); 966 985 newSource = await new ChromecastSource(name, compositeConfig as ChromecastSourceConfig, this.internalConfig, this.emitter); 967 986 break; 968 987 case 'musikcube': 988 + const {MusikcubeSource} = (await import('./MusikcubeSource.js')); 969 989 newSource = await new MusikcubeSource(name, compositeConfig as MusikcubeSourceConfig, this.internalConfig, this.emitter); 970 990 break; 971 991 case 'musiccast': 992 + const {MusicCastSource} = (await import('./MusicCastSource.js')); 972 993 newSource = await new MusicCastSource(name, compositeConfig as MusicCastSourceConfig, this.internalConfig, this.emitter); 973 994 break; 974 995 case 'mpd': 996 + const {MPDSource} = (await import('./MPDSource.js')); 975 997 newSource = await new MPDSource(name, compositeConfig as MPDSourceConfig, this.internalConfig, this.emitter); 976 998 break; 977 999 case 'vlc': 1000 + const {VLCSource} = (await import('./VLCSource.js')); 978 1001 newSource = await new VLCSource(name, compositeConfig as VLCSourceConfig, this.internalConfig, this.emitter); 979 1002 break; 980 1003 case 'azuracast': 1004 + const {AzuracastSource} = (await import('./AzuracastSource.js')); 981 1005 newSource = await new AzuracastSource(name, compositeConfig as AzuracastSourceConfig, this.internalConfig, this.emitter); 982 1006 break; 983 1007 case 'koito': 1008 + const KoitoSource = (await import('./KoitoSource.js')).default; 984 1009 newSource = await new KoitoSource(name, compositeConfig as KoitoSourceConfig, this.internalConfig, this.emitter); 985 1010 break; 986 1011 case 'maloja': 1012 + const MalojaSource = (await import('./MalojaSource.js')).default; 987 1013 newSource = await new MalojaSource(name, compositeConfig as MalojaSourceConfig, this.internalConfig, this.emitter); 988 1014 break; 989 1015 case 'tealfm': 1016 + const TealfmSource = (await import('./TealfmSource.js')).default; 990 1017 newSource = await new TealfmSource(name, compositeConfig as TealSourceConfig, this.internalConfig, this.emitter); 991 1018 break; 992 1019 case 'rocksky': 1020 + const RockskySource = (await import('./RockskySource.js')).default; 993 1021 newSource = await new RockskySource(name, compositeConfig as RockskySourceConfig, this.internalConfig, this.emitter); 994 1022 break; 995 1023 default: