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

feat: Implement random object generator

FoxxMD (Mar 20, 2026, 4:31 PM UTC) ed85d351 7d69e77a

+64 -2
+7 -1
src/core/DataUtils.ts
··· 8 8 }, 9 9 cloneDiffValues: true 10 10 //omitRemovedValues: true 11 - });export const formatNumber = (val: number | string, options?: numberFormatOptions) => { 11 + }); 12 + 13 + export const formatNumber = (val: number | string, options?: numberFormatOptions) => { 12 14 const { 13 15 toFixed = 2, defaultVal = null, prefix = '', suffix = '', round, 14 16 } = options || {}; ··· 43 45 return `${prefixStr}${localeString}${suffix}`; 44 46 }; 45 47 48 + export const generateArray = (size: number, gen: (index: number) => any) => { 49 + return Array.from(Array(size), (v,k) => gen(k)); 50 + } 51 +
+57 -1
src/core/tests/utils/fixtures.ts
··· 1 1 import { Traverse, TraverseContext } from 'neotraverse/modern'; 2 + import { faker } from '@faker-js/faker'; 2 3 import dayjs from 'dayjs'; 3 - import { AmbPlayObject, JsonPlayObject, PlayObject, PlayProgressAmb, REGEX_ISO8601_LOOSE } from '../../Atomic.js'; 4 + import { AmbPlayObject, JsonPlayObject, ObjectPlayData, PlayMeta, PlayObject, PlayProgressAmb, REGEX_ISO8601_LOOSE } from '../../Atomic.js'; 4 5 import { ListenRange } from '../../../backend/sources/PlayerState/ListenRange.js'; 5 6 import { ListenProgressPositional, ListenProgressTS } from '../../../backend/sources/PlayerState/ListenProgress.js'; 6 7 import { clone } from 'jsondiffpatch'; 8 + import { MarkOptional } from 'ts-essentials'; 7 9 8 10 interface BlockPath { key: string, parent: string }; 9 11 type BlockPaths = BlockPath[]; ··· 78 80 } 79 81 }); 80 82 return cloned as unknown as PlayObject; 83 + } 84 + 85 + export interface GeneratePlayWithLifecycleOptions { 86 + original: { 87 + data?: ObjectPlayData, 88 + meta?: MarkOptional<PlayMeta, 'lifecycle'> 89 + } 90 + } 91 + export const generatePlayWithLifecycle = (opts: GeneratePlayWithLifecycleOptions) => { 92 + 93 + } 94 + 95 + export interface RandomObjOptions { 96 + maxObjSize?: number 97 + maxKeyLength?: number 98 + keyCount?: number 99 + maxDepth?: number 100 + } 101 + 102 + const generateRandomVal = (depth: number = 0, opt: RandomObjOptions = {}, typeId?: number) => { 103 + const i = typeId ?? faker.number.int({ min: 1, max: depth > (opt.maxDepth ?? 3) ? 6 : 8 }); 104 + switch (i) { 105 + case 1: 106 + return undefined; 107 + case 2: 108 + return faker.datatype.boolean(); 109 + case 3: 110 + return faker.date.recent().toISOString(); 111 + case 4: 112 + return faker.lorem.word(); 113 + case 5: 114 + return faker.lorem.sentences({ min: 0, max: 5 }); 115 + case 6: 116 + return faker.helpers.arrayElement([faker.number.int({ min: 1, max: 1000 }), faker.number.float()]); 117 + case 7: 118 + return generateRandomObj(depth + 1, opt); 119 + case 8: 120 + const typeId = faker.number.int({ min: 4, max: depth > (opt.maxDepth ?? 3) ? 6 : 7 }); 121 + return faker.helpers.multiple(() => generateRandomVal(depth + 1, opt, typeId), { count: { min: 1, max: opt.maxObjSize ?? 7 } }) 122 + } 123 + } 124 + 125 + export const generateRandomObj = (depth: number = 0, opt: RandomObjOptions = {}) => { 126 + const tgrt: any = {} 127 + 128 + const keyCount = opt.keyCount ?? faker.number.int({ min: 1, max: 13 }); 129 + 130 + for (let i = 0; i < keyCount; i++) { 131 + const key = faker.lorem.slug({ min: 1, max: 3 }) 132 + if (tgrt[key] === undefined) { 133 + tgrt[key] = generateRandomVal(depth + 1, opt) 134 + } 135 + } 136 + return tgrt 81 137 }