Something went wrong. Try again.
[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
Something went wrong. Try again.
7.2 kB · 160 lines
TypeScript
at debugStartup
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160import { childLogger, type Logger } from "@foxxmd/logging";import type AbstractTransformer from "./AbstractTransformer.ts";import type {TransformerCommonConfig} from "../../../core/Atomic.ts";import UserTransformer from "./UserTransformer.ts";import type {StageConfig} from "../../../core/Transform.ts";import type {PlayObject} from "../../../core/Atomic.ts";import { isStageTyped } from "../../utils/PlayTransformUtils.ts";import type { MSCache } from "../Cache.ts";import NativeTransformer from "./NativeTransformer.ts";import MusicbrainzTransformer, { configFromEnv, type MusicbrainzTransformerConfig } from "./MusicbrainzTransformer.ts";import { AsyncLocalStorage } from 'node:async_hooks';import { nanoid } from "nanoid";import { SimpleError, StageTransformError } from "../errors/MSErrors.ts";
export default class TransformerManager {
protected logger: Logger; protected parentLogger: Logger; protected transformers: Map<string, AbstractTransformer[]> = new Map(); protected cache: MSCache; protected asyncStore: AsyncLocalStorage<string>;
public constructor(logger: Logger, cache: MSCache) { this.logger = childLogger(logger, 'Transformer Manager'); this.parentLogger = logger; this.cache = cache; this.asyncStore = new AsyncLocalStorage(); }
public register(config: TransformerCommonConfig): void { let transformers: AbstractTransformer[] = []; if (!this.transformers.has(config.type)) { this.transformers.set(config.type, []); } else { transformers = this.transformers.get(config.type); }
if (config.name !== undefined && transformers.some(x => x.config.name === config.name)) { throw new Error(`Cannot register ${config.type} transformer with name '${config.name}' because an existing transformer already has that name`); } const tName = config.name ?? `unnamed-${transformers.length + 1}`;
this.logger.verbose(`Registering ${config.type} transformer with name '${tName}'`);
const tLogger = childLogger(this.parentLogger, ['Transformer', () => this.asyncStore.getStore() ?? undefined]);
let t: AbstractTransformer; switch (config.type) { case 'user': t = new UserTransformer({ name: tName, ...config }, {logger: tLogger, regexCache: this.cache.regexCache, cache: this.cache.cacheTransform}); break; case 'native': t = new NativeTransformer({ name: tName, ...config }, {logger: tLogger, regexCache: this.cache.regexCache, cache: this.cache.cacheTransform}); break; case 'musicbrainz': t = new MusicbrainzTransformer({ name: tName, ...config as MusicbrainzTransformerConfig }, {logger: tLogger, regexCache: this.cache.regexCache, cache: this.cache.cacheTransform}); break; default: throw new Error(`No transformer of type '${config.type}' exists.`); } this.transformers.set(config.type, [...transformers, t]); this.logger.verbose(`${config.type} transformer with name '${tName}' registered`); }
public async registeryDefaults() { if(!this.hasTransformerType('user')) { this.register({type: 'user', name: 'MSDefault'}); } if(!this.hasTransformerType('native')) { this.register({type: 'native', name: 'MSDefault'}); } }
public async registerFromEnv() { try { const mbConfig = configFromEnv(this.logger); if(mbConfig !== undefined) { this.register(mbConfig); } else { this.logger.debug('No transformers to build from ENV'); } } catch (e) { if(e instanceof SimpleError) { this.logger.error(`Unable to build Musicbrainz Transformer from ENV: ${e.message}`); } this.logger.error(new Error('Unable to build Musicbrainz Transformer from ENV', {cause: e})); } }
public async initTransformers() { this.logger.verbose('Initializing transformers...'); for (const list of this.transformers.values()) { for (const transformer of list) { if (!transformer.isReady()) { if (!transformer.canAuthUnattended()) { transformer.logger.warn({ label: 'Heartbeat' }, 'Transformer is not ready but will not try to initialize because auth state is not good and cannot be correct unattended.'); } try { await transformer.initialize({ force: false, notify: true, notifyTitle: 'Could not initialize automatically' }); } catch (e) { transformer.logger.error(new Error('Could not initialize source automatically', { cause: e })); } } } } this.logger.verbose('Done initializing transformers'); }
public hasTransformerType(type: string): boolean { return this.transformers.has(type); }
public getTransformerType(type: string): AbstractTransformer[] | undefined { return this.transformers.get(type); }
public getTransformerByStage(data: StageConfig): AbstractTransformer { const list = this.transformers.get(data.type); if (list === undefined || list.length === 0) { throw new Error(`No transformer of type '${data.type}' is registered.`); }
if (list.length > 1) { if(data.name === undefined) { this.logger.warn(`More than one '${data.type}' transformer but name was not specified, using first registered`); return list[0]; } else { const namedTransformers = list.find(x => x.name.toLocaleLowerCase().trim() === data.name.toLocaleLowerCase().trim()); if(namedTransformers === undefined) { throw new SimpleError(`Component wanted transformer type ${data.type} with name ${data.name}. Transforms of this type are registered but none have this name.`); } return namedTransformers; } } else { return list[0] } }
public parseTransformerConfig(data: any) { if (!isStageTyped(data)) { throw new Error(`Must be an object with a 'type' property.`); } const t = this.getTransformerByStage(data); const config = t.parseConfig(data); config.stageHash = t.configHash; return config; }
public async handleStage(data: StageConfig, play: PlayObject, asyncId: string = nanoid(6)): Promise<[PlayObject, string]> { const t: AbstractTransformer = this.getTransformerByStage(data); try { const transformedPlay = await this.asyncStore.run(asyncId, async () => { return await t.handle(data, play); }); return [transformedPlay, t.name]; } catch (e) { throw new StageTransformError(t.name, 'Stage processing stopped early', {cause: e}); } }}