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.
5.5 kB · 131 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131import { childLogger, type Logger } from "@foxxmd/logging";import type { OptionalCacheUsage, PlayObject, TransformerCommon, TransformerCommonConfig } from "../../../core/Atomic.ts";import { isStageTyped, testWhenConditions } from "../../utils/PlayTransformUtils.ts";import AbstractInitializable from "../AbstractInitializable.ts";import type { StageConfig } from "../../../core/Transform.ts";import type { cacheFunctions} from "@foxxmd/regex-buddy-core";import { parseToRegexOrLiteralSearch, testMaybeRegex, searchAndReplace} from "@foxxmd/regex-buddy-core";import type { Cacheable } from "cacheable";import { hashObject } from "../../utils/StringUtils.ts";import { playContentInvariantTransform } from "../../utils/PlayComparisonUtils.ts";import { SkipTransformStageError, StagePrerequisiteError } from "../errors/MSErrors.ts";import { capitalize } from "../../../core/StringUtils.ts";import type { StaggerOptions } from "../../utils/AsyncUtils.ts";
export interface TransformerOptions { logger: Logger regexCache?: ReturnType<typeof cacheFunctions> cache: Cacheable}
export interface RegexObject { parseToRegexOrLiteralSearch: typeof parseToRegexOrLiteralSearch testMaybeRegex: typeof testMaybeRegex, searchAndReplace: typeof searchAndReplace}
export default abstract class AbstractTransformer<T = any, Y extends StageConfig = StageConfig> extends AbstractInitializable {
declare config: TransformerCommonConfig; configHash: string;
transformType: string
regex: RegexObject cache: Cacheable;
name: string;
public staggerOpts: Partial<StaggerOptions> = { initialInterval: 0, maxRandomStagger: 0};
public constructor(config: TransformerCommon, options: TransformerOptions) { super(config); this.name = config.name; this.transformType = config.type; this.regex = options.regexCache ?? { searchAndReplace, testMaybeRegex, parseToRegexOrLiteralSearch }; this.cache = options.cache; this.configHash = hashObject(this.config); this.logger = childLogger(options.logger, [this.getIdentifier()]); }
protected getIdentifier() { return `${capitalize(this.transformType)} - ${this.name}` }
public parseConfig(data: any): Y { if (!isStageTyped(data)) { throw new Error(`Must be an object with a 'type' property.`); } return this.doParseConfig(data); }
protected abstract doParseConfig(data: StageConfig): Y;
public async handle(data: Y, play: PlayObject, opts?: OptionalCacheUsage): Promise<PlayObject> { const { useCachedResult = true, } = (opts ?? {}); const cacheKey = `transformResult-${this.configHash}-${hashObject(data)}-${hashObject(playContentInvariantTransform(play))}` try { const cachedTransformData = useCachedResult ? await this.cache.get<T>(cacheKey) : undefined; if(cachedTransformData !== undefined) { this.logger.debug('Transform cache hit'); const transformed = await this.doHandle(data, play, cachedTransformData, opts); return transformed; } } catch (e) { this.logger.warn(new Error(`Could not fetch cache key ${cacheKey}`, {cause: e})); }
if (data.when !== undefined) { if (!testWhenConditions(data.when, play, { testMaybeRegex: this.regex.testMaybeRegex })) { await this.cache.set(cacheKey, play, this.config.options?.ttl ?? '15s'); throw new SkipTransformStageError('When condition not met', {shortStack: true}); } }
try { await this.handlePreFetch(play, data, opts); } catch (e) { if(e instanceof SkipTransformStageError) { await this.cache.set(cacheKey, play, this.config.options?.ttl ?? '15s'); } throw new Error('preFetch check did not pass', { cause: e }); }
let transformData: T; let fetchedTransformData: any; try { fetchedTransformData = await this.getTransformerData(play, data, opts); } catch (e) { throw new Error(`Could not fetch transformer data`, { cause: e }); }
try { transformData = await this.handlePostFetch(play, fetchedTransformData, data, opts); } catch (e) { if(e instanceof StagePrerequisiteError) { await this.cache.set(cacheKey, play, this.config.options?.ttl ?? '15s'); } throw new Error('postFetch did not pass', { cause: e }); }
const transformed = await this.doHandle(data, play, transformData, opts); await this.cache.set(cacheKey, transformData, this.config.options?.ttl ?? '15s'); return transformed; }
protected abstract doHandle(data: StageConfig, play: PlayObject, transformData: T, opts?: OptionalCacheUsage): Promise<PlayObject>;
public async getTransformerData(play: PlayObject, stageConfig: Y, opts?: OptionalCacheUsage): Promise<any> { return undefined; }
public async handlePostFetch(play: PlayObject, transformData: any, stageConfig: Y, opts?: OptionalCacheUsage): Promise<T> { return transformData; }
public async handlePreFetch(play: PlayObject, stageConfig: Y, opts?: OptionalCacheUsage): Promise<void> { return }}