mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-09-03 05:10:00 +03:00
Refactor metadata stage so atomic parts are objects or booleans
This commit is contained in:
@@ -6,7 +6,7 @@ export interface ConditionalSearchAndReplaceRegExp extends SearchAndReplaceRegEx
|
||||
|
||||
export type ConditionalSearchAndReplaceTerm = Omit<ConditionalSearchAndReplaceRegExp, 'test'>
|
||||
export type SearchAndReplaceTerm = string | ConditionalSearchAndReplaceTerm;
|
||||
export type ExternalMetadataTerm = true | undefined | { when: WhenConditionsConfig };
|
||||
export type ExternalMetadataTerm = boolean | undefined | { when: WhenConditionsConfig };
|
||||
|
||||
export type PlayTransformParts<T, Y = MaybeStageTyped> = Extract<PlayTransformStage<T[]>, Y> & { when?: WhenConditionsConfig };
|
||||
export type PlayTransformUserParts<T> = PlayTransformUserStage<T[]> & { when?: WhenConditionsConfig };
|
||||
@@ -21,8 +21,12 @@ export interface PlayTransformPartsAtomic<T> {
|
||||
album?: T
|
||||
}
|
||||
|
||||
export type StageType = 'spotify' | 'listenbrainz' | 'native' | 'user';
|
||||
export const STAGE_TYPES: StageType[] = ['spotify','listenbrainz','native','user']
|
||||
export type StageTypeMetadata = 'spotify' | 'listenbrainz' | 'native';
|
||||
export type StageTypeUser = 'user';
|
||||
export type StageType = StageTypeMetadata | StageTypeUser;
|
||||
export const STAGE_TYPES_USER: StageTypeUser[] = ['user'];
|
||||
export const STAGE_TYPES_METADATA: StageTypeMetadata[] = ['spotify','listenbrainz','native'];
|
||||
export const STAGE_TYPES: StageType[] = [...STAGE_TYPES_METADATA, ...STAGE_TYPES_USER];
|
||||
|
||||
export interface StageTyped {
|
||||
type: StageType
|
||||
@@ -38,14 +42,14 @@ export interface PlayTransformStageTyped<T> extends PlayTransformPartsAtomic<T>
|
||||
type: StageType
|
||||
}
|
||||
|
||||
export interface PlayTransformMetadataStage extends PlayTransformStageTyped<ExternalMetadataTerm[]> {
|
||||
export interface PlayTransformMetadataStage extends PlayTransformStageTyped<ExternalMetadataTerm> {
|
||||
score?: number
|
||||
all?: ExternalMetadataTerm
|
||||
type: 'spotify' | 'listenbrainz' | 'native'
|
||||
// all?: ExternalMetadataTerm
|
||||
type: StageTypeMetadata
|
||||
}
|
||||
|
||||
export interface PlayTransformUserStage<T> extends PlayTransformStageTyped<T> {
|
||||
type: 'user'
|
||||
type: StageTypeUser
|
||||
}
|
||||
export type UntypedPlayTransformUserStage<T> = Omit<PlayTransformUserStage<T>, 'type'> & {type?: never};
|
||||
|
||||
|
||||
@@ -4,12 +4,13 @@ import asPromised from 'chai-as-promised';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import AbstractComponent from "../../common/AbstractComponent.js";
|
||||
|
||||
import { ConditionalSearchAndReplaceRegExp, TRANSFORM_HOOK } from "../../common/infrastructure/Transform.js";
|
||||
import { ConditionalSearchAndReplaceRegExp, STAGE_TYPES, STAGE_TYPES_METADATA, TRANSFORM_HOOK } from "../../common/infrastructure/Transform.js";
|
||||
|
||||
import { isConditionalSearchAndReplace } from "../../utils/PlayTransformUtils.js";
|
||||
import { asPlays, generatePlay, normalizePlays } from "../utils/PlayTestUtils.js";
|
||||
import { WebhookPayload } from "../../common/infrastructure/config/health/webhooks.js";
|
||||
import { findCauseByMessage } from "../../utils/ErrorUtils.js";
|
||||
import { ComAtprotoServerDescribeServer } from "@atproto/api";
|
||||
|
||||
chai.use(asPromised);
|
||||
|
||||
@@ -220,6 +221,37 @@ describe('Play Transforms', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Non-User Stage Parsing', function () {
|
||||
|
||||
describe('Non-User Stage Types', function () {
|
||||
|
||||
for(const t of STAGE_TYPES_METADATA) {
|
||||
|
||||
it(`Allows non-user Stage Type ${t}`, function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
type: t,
|
||||
title: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => component.buildTransformRules()).to.not.throw();
|
||||
expect(component.transformRules.preCompare).to.be.an('array');
|
||||
expect(component.transformRules.preCompare).to.be.length(1);
|
||||
expect(component.transformRules.preCompare).to.have.nested.property('0.type');
|
||||
expect(component.transformRules.preCompare[0].type).eq(t);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Play Transforming', function () {
|
||||
|
||||
it('Returns original play if no hooks are defined', function () {
|
||||
@@ -230,136 +262,139 @@ describe('Play Transforms', function () {
|
||||
expect(JSON.stringify(play)).equal(JSON.stringify(transformed));
|
||||
});
|
||||
|
||||
it('Transforms when hook is present', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
title: ['something']
|
||||
describe('User Play Transforming', function () {
|
||||
it('Transforms when hook is present', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
title: ['something']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
component.buildTransformRules();
|
||||
component.buildTransformRules();
|
||||
|
||||
const play = generatePlay({ track: 'My coolsomething track' });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.track).equal('My cool track');
|
||||
});
|
||||
const play = generatePlay({ track: 'My coolsomething track' });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.track).equal('My cool track');
|
||||
});
|
||||
|
||||
it('Transforms consecutively when hook is present with multiple values', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
title: ['something', 'cool']
|
||||
it('Transforms consecutively when hook is present with multiple values', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
title: ['something', 'cool']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
component.buildTransformRules();
|
||||
component.buildTransformRules();
|
||||
|
||||
const play = generatePlay({ track: 'My coolsomething track' });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.track).equal('My track');
|
||||
});
|
||||
const play = generatePlay({ track: 'My coolsomething track' });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.track).equal('My track');
|
||||
});
|
||||
|
||||
it('Transforms using parsed regex', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
title: [
|
||||
{
|
||||
search: '/(cool )(some)(thing)/i',
|
||||
replace: '$1$3'
|
||||
}
|
||||
]
|
||||
it('Transforms using parsed regex', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
title: [
|
||||
{
|
||||
search: '/(cool )(some)(thing)/i',
|
||||
replace: '$1$3'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
component.buildTransformRules();
|
||||
component.buildTransformRules();
|
||||
|
||||
const play = generatePlay({ track: 'My cool something track' });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.track).equal('My cool thing track');
|
||||
});
|
||||
const play = generatePlay({ track: 'My cool something track' });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.track).equal('My cool thing track');
|
||||
});
|
||||
|
||||
|
||||
it('Transforms using parsed regex to get primary artist from delimited artist string', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
artists: [
|
||||
{
|
||||
search: '/(.*?)(\\s*\\/\\s*)(.*$)/i',
|
||||
replace: '$1'
|
||||
}
|
||||
]
|
||||
it('Transforms using parsed regex to get primary artist from delimited artist string', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
artists: [
|
||||
{
|
||||
search: '/(.*?)(\\s*\\/\\s*)(.*$)/i',
|
||||
replace: '$1'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
component.buildTransformRules();
|
||||
component.buildTransformRules();
|
||||
|
||||
const play = generatePlay({ artists: ['My Artist One / My Artist Two / Another Guy'] });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.artists).length(1)
|
||||
expect(transformed.data.artists[0]).equal('My Artist One');
|
||||
});
|
||||
const play = generatePlay({ artists: ['My Artist One / My Artist Two / Another Guy'] });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.artists).length(1)
|
||||
expect(transformed.data.artists[0]).equal('My Artist One');
|
||||
});
|
||||
|
||||
it('Removes title when transform replaces with empty string', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
title: ['something']
|
||||
it('Removes title when transform replaces with empty string', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
title: ['something']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
component.buildTransformRules();
|
||||
component.buildTransformRules();
|
||||
|
||||
const play = generatePlay({ track: 'something' });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.track).is.undefined;
|
||||
});
|
||||
const play = generatePlay({ track: 'something' });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.track).is.undefined;
|
||||
});
|
||||
|
||||
it('Removes album when transform replaces with empty string', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
album: ['something']
|
||||
it('Removes album when transform replaces with empty string', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
album: ['something']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
component.buildTransformRules();
|
||||
component.buildTransformRules();
|
||||
|
||||
const play = generatePlay({ album: 'something' });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.album).is.undefined;
|
||||
});
|
||||
const play = generatePlay({ album: 'something' });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.album).is.undefined;
|
||||
});
|
||||
|
||||
it('Removes an artist when transform replaces with empty string', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
artists: ['something']
|
||||
it('Removes an artist when transform replaces with empty string', function () {
|
||||
component.config = {
|
||||
options: {
|
||||
playTransform: {
|
||||
preCompare: {
|
||||
artists: ['something']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
component.buildTransformRules();
|
||||
component.buildTransformRules();
|
||||
|
||||
const play = generatePlay({ artists: ['something', 'big'] });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.artists!.length).is.eq(1)
|
||||
expect(transformed.data.artists![0]).is.eq('big')
|
||||
});
|
||||
|
||||
const play = generatePlay({ artists: ['something', 'big'] });
|
||||
const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare);
|
||||
expect(transformed.data.artists!.length).is.eq(1)
|
||||
expect(transformed.data.artists![0]).is.eq('big')
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
WhenConditionsConfig,
|
||||
WhenParts
|
||||
} from "../common/infrastructure/Transform.js";
|
||||
import e from "express";
|
||||
|
||||
export const isWhenCondition = (val: unknown): val is WhenParts<string> => {
|
||||
if (val !== null && typeof val === 'object') {
|
||||
@@ -78,10 +77,7 @@ export const isExternalMetadataTerm = (val: unknown): val is ExternalMetadataTer
|
||||
}
|
||||
const tf = typeof val;
|
||||
if(tf === 'boolean') {
|
||||
if(val === true) {
|
||||
return true;
|
||||
}
|
||||
throw new Error(`Value must be one of: true, undefined, or object with 'when'`);
|
||||
return true;
|
||||
}
|
||||
if(tf === null) {
|
||||
throw new Error(`Value is null but must be one of: true, undefined, or object with 'when'`);
|
||||
@@ -92,34 +88,36 @@ export const isExternalMetadataTerm = (val: unknown): val is ExternalMetadataTer
|
||||
}
|
||||
throw new Error(`Value is not a proper 'when' object`);
|
||||
}
|
||||
throw new Error(`Value is type of ${tf} but must be one of: true, undefined, or object with 'when'`);
|
||||
throw new Error(`Value is type of ${tf} but must be one of: boolean, undefined, or object with 'when'`);
|
||||
}
|
||||
|
||||
export const isPlayTransformStage = (val: object | Partial<PlayTransformStage<SearchAndReplaceTerm[]>>): val is PlayTransformStage<SearchAndReplaceTerm[]> => {
|
||||
if(!('type' in val)) {
|
||||
if (!('type' in val)) {
|
||||
throw new Error(`Stage is missing 'type'. Must be one of: ${STAGE_TYPES.join(', ')}`);
|
||||
}
|
||||
if(!STAGE_TYPES.includes(val.type)) {
|
||||
if (!STAGE_TYPES.includes(val.type)) {
|
||||
throw new Error(`Stage has invalid 'type'. Must be one of: ${STAGE_TYPES.join(', ')}`);
|
||||
}
|
||||
|
||||
for(const k of ['artist', 'title', 'album']) {
|
||||
if(!(k in val)) {
|
||||
for (const k of ['artist', 'title', 'album']) {
|
||||
if (!(k in val)) {
|
||||
continue;
|
||||
}
|
||||
if(!Array.isArray(val[k])) {
|
||||
throw new Error(`${k} must be an array`);
|
||||
}
|
||||
for(const term of val[k]) {
|
||||
if (val.type === 'user') {
|
||||
if (!Array.isArray(val[k])) {
|
||||
throw new Error(`${k} must be an array`);
|
||||
}
|
||||
try {
|
||||
if(val.type === 'user') {
|
||||
isSearchAndReplaceTerm(val[k]);
|
||||
} else {
|
||||
isExternalMetadataTerm(val[k]);
|
||||
}
|
||||
|
||||
isSearchAndReplaceTerm(val[k]);
|
||||
} catch (e) {
|
||||
throw new Error(`Property '${k}' was not a valid type`, {cause: e});
|
||||
throw new Error(`Property '${k}' was not a valid type`, { cause: e });
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
isExternalMetadataTerm(val[k]);
|
||||
|
||||
} catch (e) {
|
||||
throw new Error(`Property '${k}' was not a valid type`, { cause: e });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user