From 5c217b2fbcb59baca2e7cdfda9ab76ae945853da Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Fri, 18 Sep 2026 00:28:30 +0000 Subject: [PATCH] refactor(transform): separate rocksky transformer from util/types to prepare for dynamic import --- .../common/transforms/TransformerManager.ts | 4 +- .../{ => rocksky}/RockskyTransformer.ts | 139 +++--------------- .../rocksky/RockskyTransformerUtil.ts | 75 ++++++++++ src/backend/common/vendor/RockSkyApiClient.ts | 6 +- .../common/vendor/rocksky/interfaces.ts | 29 +++- src/backend/tests/rocksky/rocksky.test.ts | 6 +- 6 files changed, 129 insertions(+), 130 deletions(-) rename src/backend/common/transforms/{ => rocksky}/RockskyTransformer.ts (87%) create mode 100644 src/backend/common/transforms/rocksky/RockskyTransformerUtil.ts diff --git a/src/backend/common/transforms/TransformerManager.ts b/src/backend/common/transforms/TransformerManager.ts index 136a5a27..ecd8690f 100644 --- a/src/backend/common/transforms/TransformerManager.ts +++ b/src/backend/common/transforms/TransformerManager.ts @@ -11,7 +11,9 @@ import MusicbrainzTransformer, { configFromEnv, type MusicbrainzTransformerConfi import { AsyncLocalStorage } from 'node:async_hooks'; import { nanoid } from "nanoid"; import { SimpleError, StageTransformError } from "../errors/MSErrors.ts"; -import RockskyTransformer, { type RockskyTransformerConfig, configFromEnv as rsConfigFromEnv } from "./RockskyTransformer.ts"; +import RockskyTransformer from "./rocksky/RockskyTransformer.ts"; +import { configFromEnv as rsConfigFromEnv } from "./rocksky/RockskyTransformerUtil.ts"; +import { type RockskyTransformerConfig } from "../vendor/rocksky/interfaces.ts"; export default class TransformerManager { diff --git a/src/backend/common/transforms/RockskyTransformer.ts b/src/backend/common/transforms/rocksky/RockskyTransformer.ts similarity index 87% rename from src/backend/common/transforms/RockskyTransformer.ts rename to src/backend/common/transforms/rocksky/RockskyTransformer.ts index 1b46b755..d7a1b6e0 100644 --- a/src/backend/common/transforms/RockskyTransformer.ts +++ b/src/backend/common/transforms/rocksky/RockskyTransformer.ts @@ -1,54 +1,31 @@ -import { type ArtistCredit, DEFAULT_ROCKSKY_MISSING_TYPES, type LifecycleInput, type OptionalCacheUsage, type PlayObject, rockskyMissingFields, type RockskyMissingField, type TrackMeta, type TransformerCommon, type TransformOptions } from "../../../core/Atomic.ts"; -import { isWhenCondition, testWhenConditions } from "../../utils/PlayTransformUtils.ts"; -import type {WebhookPayload} from "../infrastructure/config/health/webhooks.ts"; -import type {ExternalMetadataTerm, PlayTransformMetadataStage} from "../../../core/Transform.ts"; -import AtomicPartsTransformer from "./AtomicPartsTransformer.ts"; -import type {TransformerOptions} from "./AbstractTransformer.ts"; -import { ARTIST_WEIGHT, TITLE_WEIGHT } from "../infrastructure/Atomic.ts"; -import { DELIMITERS } from '../../../core/Atomic.ts'; -import { MaybeLogger } from '../MaybeLogger.ts'; +import { type ArtistCredit, DEFAULT_ROCKSKY_MISSING_TYPES, type LifecycleInput, type OptionalCacheUsage, type PlayObject, rockskyMissingFields, type RockskyMissingField, type TrackMeta } from "../../../../core/Atomic.ts"; +import { isWhenCondition, testWhenConditions } from "../../../utils/PlayTransformUtils.ts"; +import type {WebhookPayload} from "../../infrastructure/config/health/webhooks.ts"; +import type {ExternalMetadataTerm, PlayTransformMetadataStage} from "../../../../core/Transform.ts"; +import AtomicPartsTransformer from "../AtomicPartsTransformer.ts"; +import type {TransformerOptions} from "../AbstractTransformer.ts"; +import { ARTIST_WEIGHT, TITLE_WEIGHT } from "../../infrastructure/Atomic.ts"; +import { DELIMITERS } from '../../../../core/Atomic.ts'; +import { MaybeLogger } from '../../MaybeLogger.ts'; import { childLogger } from "@foxxmd/logging"; -import { type UsingTypes } from "../vendor/musicbrainz/MusicbrainzApiClientPool.ts"; -import { difference } from "../../utils.ts"; -import { removeUndefinedKeys } from '../../../core/DataUtils.ts'; -import { SimpleError, SkipTransformStageError, StagePrerequisiteError, StageTransformError } from "../errors/MSErrors.ts"; -import { scoreNormalizedStringsWeighted } from "../../utils/StringUtils.ts"; +import { type UsingTypes } from "../../vendor/musicbrainz/MusicbrainzApiClientPool.ts"; +import { difference } from "../../../utils.ts"; +import { removeUndefinedKeys } from '../../../../core/DataUtils.ts'; +import { SimpleError, SkipTransformStageError, StagePrerequisiteError, StageTransformError } from "../../errors/MSErrors.ts"; +import { scoreNormalizedStringsWeighted } from "../../../utils/StringUtils.ts"; import clone from "clone"; import type { Cacheable } from "cacheable"; -import { artistCreditsToNames, splitByFirstRegexFound } from "../../../core/StringUtils.ts"; -import { nativeParse } from "./NativeTransformer.ts"; -import { comparePlayArtistsNormalized, scoreTrackWeightedAndNormalized } from "../../utils/PlayComparisonUtils.ts"; -import * as z from 'zod'; -import { hasRequiredScrobbleFields, hasScrobbleConfidenceFields, songViewToPlay } from "../vendor/RockSkyApiClient.ts"; +import { artistCreditsToNames, splitByFirstRegexFound } from "../../../../core/StringUtils.ts"; +import { nativeParse } from "../NativeTransformer.ts"; +import { comparePlayArtistsNormalized, scoreTrackWeightedAndNormalized } from "../../../utils/PlayComparisonUtils.ts"; +import { hasRequiredScrobbleFields, hasScrobbleConfidenceFields, songViewToPlay } from "../../vendor/RockSkyApiClient.ts"; import { RockskyError, type SongMatchView, type SongViewDetailed } from "@rocksky/sdk"; -import { RockskyClientPool } from "../vendor/rocksky/RockskyClientWrapped.ts"; -import type { RockskyApiClientConfig } from "../vendor/rocksky/interfaces.ts"; - -const searchType = z.enum(['basic','basicorids','mbid','isrc','artist']); -export type SearchType = z.infer; +import { RockskyClientPool } from "../../vendor/rocksky/RockskyClientWrapped.ts"; +import type { RockskyTransformerConfig, RockskyTransformerData } from "../../vendor/rocksky/interfaces.ts"; +import { type SearchType, searchType } from "./RockskyTransformerUtil.ts"; export const DEFAULT_SEARCHTYPE_ORDER: SearchType[] = ['isrc','basic']; -export interface RockskyTransformerData { - searchWhenMissing?: RockskyMissingField[] - forceSearch?: boolean - score?: number - allowNoMatch?: boolean - logPreMbid?: boolean - searchOrder?: SearchType[] - searchArtistMethod?: ('naive' | 'native') - - /** Ignore album artist if it is "Various Artists" - * - * @default true - */ - ignoreVA?: boolean - - titleWeight?: number | true - artistWeight?: number | true - albumWeight?: number | true -} - export interface RockskyTransformerDataStrong extends RockskyTransformerData { searchWhenMissing: RockskyMissingField[] @@ -60,8 +37,6 @@ export interface RockskyTransformerDataStrong extends RockskyTransformerData { export interface RockskyTransformerDataStage extends RockskyTransformerDataStrong,PlayTransformMetadataStage { } -export type RockskyTransformerConfig = TransformerCommon & {options?: TransformOptions & {logUrl?: boolean}} - interface SongViewDetailedMS extends SongViewDetailed { requestQuery: string requestQueries?: LifecycleInput[] @@ -644,78 +619,6 @@ export const rankSongMatchesByPriority = (list: SongMatchView[], stageConfig: Ro return cList; }; -export const DEFAULTS_NATIVE: RockskyTransformerData = { - "searchArtistMethod": "native", - "searchOrder": ["artist"] -} - -export const DEFAULTS_FIELDS_BIAS = { - "titleWeight": 0.33, - "albumWeight": 0.33, - "artistWeight": 0.33 -} - -export const DEFAULTS_PRESET: RockskyTransformerData = { - "searchOrder": ["isrc", "basic"] -} - -export const DEFAULTS_ID: RockskyTransformerData = { - "searchOrder": ["isrc", "mbid", "basicorids", "basic"] -}; - -const PRESETS: Record = { - default: DEFAULTS_PRESET, - sensible: DEFAULTS_ID, - native: DEFAULTS_NATIVE, - fields: {...DEFAULTS_FIELDS_BIAS, ...DEFAULTS_PRESET}, - 'id': DEFAULTS_ID -} - -export const configFromEnv = (logger: MaybeLogger = new MaybeLogger()) => { - const rsEnv = process.env.RS_PRESETS; - let rsConfig: RockskyTransformerConfig; - if (rsEnv !== undefined && rsEnv.trim() !== '') { - rsConfig = { - type: 'rocksky', - name: 'MSRockskyDefault', - data: { - apis: [ - { - enable: true - } - ] - }, - defaults: { - - } - } - const presets = rsEnv.split(',').map(x => x.trim().toLocaleLowerCase()); - const soSet = new Set(); - for (const pName of presets) { - const p = PRESETS[pName]; - if(p === undefined) { - logger.warn(`No preset with name '${p}'`); - continue; - } - const { searchOrder = [], ...rest } = p; - rsConfig.defaults = { - ...rsConfig.defaults, - ...rest, - } - for (const o of searchOrder) { - soSet.add(o); - } - } - - if(soSet.size > 0) { - rsConfig.defaults.searchOrder = Array.from(soSet); - } - logger.debug(`Using presets: ${presets.join(',')}`); - } - - return rsConfig; -} - export class SearchPrerequisiteError extends SimpleError { name = 'Search Prerequistie Failure'; } \ No newline at end of file diff --git a/src/backend/common/transforms/rocksky/RockskyTransformerUtil.ts b/src/backend/common/transforms/rocksky/RockskyTransformerUtil.ts new file mode 100644 index 00000000..5f27420b --- /dev/null +++ b/src/backend/common/transforms/rocksky/RockskyTransformerUtil.ts @@ -0,0 +1,75 @@ +import * as z from "zod"; +import { MaybeLogger } from "../../MaybeLogger.ts"; +import type { RockskyTransformerConfig, RockskyTransformerData } from "../../vendor/rocksky/interfaces.ts"; + +export const configFromEnv = (logger: MaybeLogger = new MaybeLogger()) => { + const rsEnv = process.env.RS_PRESETS; + let rsConfig: RockskyTransformerConfig; + if (rsEnv !== undefined && rsEnv.trim() !== '') { + rsConfig = { + type: 'rocksky', + name: 'MSRockskyDefault', + data: { + apis: [ + { + enable: true + } + ] + }, + defaults: {} + }; + const presets = rsEnv.split(',').map(x => x.trim().toLocaleLowerCase()); + const soSet = new Set(); + for (const pName of presets) { + const p = PRESETS[pName]; + if (p === undefined) { + logger.warn(`No preset with name '${p}'`); + continue; + } + const { searchOrder = [], ...rest } = p; + rsConfig.defaults = { + ...rsConfig.defaults, + ...rest, + }; + for (const o of searchOrder) { + soSet.add(o); + } + } + + if (soSet.size > 0) { + rsConfig.defaults.searchOrder = Array.from(soSet); + } + logger.debug(`Using presets: ${presets.join(',')}`); + } + + return rsConfig; +} + +export const DEFAULTS_NATIVE: RockskyTransformerData = { + "searchArtistMethod": "native", + "searchOrder": ["artist"] +}; + +export const DEFAULTS_FIELDS_BIAS = { + "titleWeight": 0.33, + "albumWeight": 0.33, + "artistWeight": 0.33 +}; + +export const DEFAULTS_PRESET: RockskyTransformerData = { + "searchOrder": ["isrc", "basic"] +}; + +export const DEFAULTS_ID: RockskyTransformerData = { + "searchOrder": ["isrc", "mbid", "basicorids", "basic"] +}; + +export const PRESETS: Record = { + default: DEFAULTS_PRESET, + sensible: DEFAULTS_ID, + native: DEFAULTS_NATIVE, + fields: { ...DEFAULTS_FIELDS_BIAS, ...DEFAULTS_PRESET }, + 'id': DEFAULTS_ID +}; +export const searchType = z.enum(['basic', 'basicorids', 'mbid', 'isrc', 'artist']);export type SearchType = z.infer; + diff --git a/src/backend/common/vendor/RockSkyApiClient.ts b/src/backend/common/vendor/RockSkyApiClient.ts index 25228ce2..fc1154ab 100644 --- a/src/backend/common/vendor/RockSkyApiClient.ts +++ b/src/backend/common/vendor/RockSkyApiClient.ts @@ -10,13 +10,12 @@ import AbstractApiClient from "./AbstractApiClient.ts"; import { isPortReachableConnect, joinedUrl, normalizeWebAddress } from '../../utils/NetworkUtils.ts'; import type {ListenResponse, ListenType, SubmitPayload} from '../../../core/vendor/listenbrainz/interfaces.ts'; import { playToListenPayload } from './listenbrainz/lzUtils.ts'; -import type {RockskyScrobble} from './rocksky/interfaces.ts'; import type {Handle} from "@atcute/lexicons"; import { getATProtoIdentifier, identifierToAtProtoHandle } from './atproto/atUtils.ts'; import { baseFormatPlayObj } from "../../utils/PlayTransformUtils.ts"; import { AuthError, ScrobbleSubmitError } from "../errors/MSErrors.ts"; import { tryApiCall } from "../../utils/RequestUtils.ts"; -import { type CreateScrobbleInput, RockskyClient, Agent, type SongViewDetailed, type ScrobbleInput } from "@rocksky/sdk"; +import { type CreateScrobbleInput, RockskyClient, Agent, type SongViewDetailed, type ScrobbleInput, type ScrobbleViewBasic } from "@rocksky/sdk"; import { getRoot } from "../../ioc.ts"; import type { MSCache } from "../Cache.ts"; import type {HandleData} from "../infrastructure/config/client/atproto.ts"; @@ -585,4 +584,5 @@ const rockskyUriToData = (str: string): { web?: string, playId?: string, user?: } } return undefined; -} \ No newline at end of file +} +export type RockskyScrobble = ScrobbleViewBasic; diff --git a/src/backend/common/vendor/rocksky/interfaces.ts b/src/backend/common/vendor/rocksky/interfaces.ts index fa52cc4b..c00c5f9c 100644 --- a/src/backend/common/vendor/rocksky/interfaces.ts +++ b/src/backend/common/vendor/rocksky/interfaces.ts @@ -1,7 +1,6 @@ -import type { ScrobbleViewBasic } from "@rocksky/sdk"; import * as z from 'zod'; - -export type RockskyScrobble = ScrobbleViewBasic; +import type { RockskyMissingField, TransformerCommon, TransformOptions } from '../../../../core/Atomic.ts'; +import type { SearchType } from "../../transforms/rocksky/RockskyTransformerUtil.ts"; const rockskyApiConfig = z.object({ /** @@ -59,4 +58,26 @@ export type RockskyApiClientConfig = z.infer; // }[] // } -export const ROCKSKY_URL = 'https://api.rocksky.app'; \ No newline at end of file +export const ROCKSKY_URL = 'https://api.rocksky.app'; +export interface RockskyTransformerData { + searchWhenMissing?: RockskyMissingField[]; + forceSearch?: boolean; + score?: number; + allowNoMatch?: boolean; + logPreMbid?: boolean; + searchOrder?: SearchType[]; + searchArtistMethod?: ('naive' | 'native'); + + /** Ignore album artist if it is "Various Artists" + * + * @default true + */ + ignoreVA?: boolean; + + titleWeight?: number | true; + artistWeight?: number | true; + albumWeight?: number | true; +} + +export type RockskyTransformerConfig = TransformerCommon & { options?: TransformOptions & { logUrl?: boolean; }; }; + diff --git a/src/backend/tests/rocksky/rocksky.test.ts b/src/backend/tests/rocksky/rocksky.test.ts index 7a236268..bc2dc442 100644 --- a/src/backend/tests/rocksky/rocksky.test.ts +++ b/src/backend/tests/rocksky/rocksky.test.ts @@ -5,15 +5,13 @@ import asPromised from 'chai-as-promised'; import { before, describe, it } from 'mocha'; import { initMemoryCache } from "../../common/Cache.ts"; import { Cacheable } from "cacheable"; -import { DEFAULT_MISSING_TYPES, DEFAULT_ROCKSKY_MISSING_TYPES, type PlayObject } from "../../../core/Atomic.ts"; +import { DEFAULT_ROCKSKY_MISSING_TYPES, type PlayObject } from "../../../core/Atomic.ts"; import { getPathFromCWD } from '../../common/index.ts'; import path from 'path'; -import { generatePlay, withBrainz } from '../../../core/tests/utils/PlayTestUtils.ts'; -import { intersect, missingMbidTypes } from '../../utils.ts'; import { artistNamesToCredits } from '../../../core/StringUtils.ts'; import type { RockskyApiClientConfig } from '../../common/vendor/rocksky/interfaces.ts'; import type { MarkRequired } from 'ts-essentials'; -import RockskyTransformer, { DEFAULT_SEARCHTYPE_ORDER } from '../../common/transforms/RockskyTransformer.ts'; +import RockskyTransformer, { DEFAULT_SEARCHTYPE_ORDER } from '../../common/transforms/rocksky/RockskyTransformer.ts'; chai.use(asPromised); -- 2.51.2