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

fix(maloja): Remove null duration/length

FoxxMD (Aug 18, 2025, 2:24 PM UTC) 4f6fb4ff 24df2278

+29 -16
+5 -5
src/backend/common/vendor/maloja/MalojaApiClient.ts
··· 8 8 import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../../infrastructure/Atomic.js"; 9 9 import { isNodeNetworkException } from "../../errors/NodeErrors.js"; 10 10 import { isSuperAgentResponseError } from "../../errors/ErrorUtils.js"; 11 - import { parseRetryAfterSecsFromObj, sleep } from "../../../utils.js"; 11 + import { getNonEmptyVal, parseRetryAfterSecsFromObj, removeUndefinedKeys, sleep } from "../../../utils.js"; 12 12 import { UpstreamError } from "../../errors/UpstreamError.js"; 13 13 import { getMalojaResponseError, isMalojaAPIErrorBody, MalojaResponseV3CommonData, MalojaScrobbleData, MalojaScrobbleRequestData, MalojaScrobbleV3RequestData, MalojaScrobbleV3ResponseData, MalojaScrobbleWarning } from "./interfaces.js"; 14 14 import { getScrobbleTsSOCDate, getScrobbleTsSOCDateWithContext } from '../../../utils/TimeUtils.js'; ··· 346 346 artists = mArtists; 347 347 time = mTime; 348 348 title = mTitle; 349 - duration = mLength; 350 - listenedFor = mDuration; 349 + duration = getNonEmptyVal(mLength); 350 + listenedFor = getNonEmptyVal(mDuration); 351 351 if (mAlbum !== null) { 352 352 const { 353 353 albumtitle, ··· 369 369 }, []); 370 370 const urlParams = new URLSearchParams([['artist', artists[0]], ['title', title]]); 371 371 return { 372 - data: { 372 + data: removeUndefinedKeys({ 373 373 artists: [...new Set(artistStrings)] as string[], 374 374 track: title, 375 375 album, 376 376 duration, 377 377 listenedFor, 378 378 playDate: dayjs.unix(time), 379 - }, 379 + }), 380 380 meta: { 381 381 source: 'Maloja', 382 382 url: {
+24 -11
src/backend/utils.ts
··· 611 611 return eArtists.length > 1 || cArtists.length > 1; 612 612 } 613 613 614 - export const getFirstNonEmptyVal = <T = unknown>(values: unknown[], options: {ofType?: string, test?: (val: T) => boolean} = {}): NonNullable<T> | undefined => { 614 + export interface NonEmptyOptions<T> { 615 + ofType?: string, 616 + test?: (val: T) => boolean 617 + } 618 + export const getFirstNonEmptyVal = <T = unknown>(values: unknown[], options: NonEmptyOptions<T> = {}): NonNullable<T> | undefined => { 615 619 for(const v of values) { 616 - if(v === undefined || v === null) { 617 - continue; 618 - } 619 - if(options.ofType !== undefined && typeof v !== options.ofType) { 620 - continue; 620 + const nonEmptyVal = getNonEmptyVal(v, options); 621 + if(nonEmptyVal !== undefined) { 622 + return nonEmptyVal as T; 621 623 } 622 - if(options.test !== undefined && options.test(v as T) === false) { 623 - continue; 624 - } 625 - return v as T; 626 624 } 627 625 return undefined; 628 626 } 629 627 630 - export const getFirstNonEmptyString = (values: unknown[]) => getFirstNonEmptyVal<string>(values, {ofType: 'string', test: (v) => v.trim() !== ''}); 628 + export const getNonEmptyVal = <T = unknown>(value: unknown, options: NonEmptyOptions<T> = {}): NonNullable<T> | undefined => { 629 + if (value === undefined || value === null) { 630 + return undefined; 631 + } 632 + if (options.ofType !== undefined && typeof value !== options.ofType) { 633 + return undefined; 634 + } 635 + if (options.test !== undefined && options.test(value as T) === false) { 636 + return undefined; 637 + } 638 + return value as T; 639 + } 640 + 641 + const nonEmptyStringOpts: NonEmptyOptions<string> = { ofType: 'string', test: (v) => v.trim() !== '' }; 642 + export const getFirstNonEmptyString = (values: unknown[]) => getFirstNonEmptyVal<string>(values, nonEmptyStringOpts); 643 + export const getNonEmptyString = (value: unknown) => getNonEmptyVal<string>(value, nonEmptyStringOpts); 631 644 632 645 /** 633 646 * Runs the function `fn`