[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(plex): Also read MBIDs from `guid` field

Some items on Plex store the MBID in the `guid` field in the `Metadata`
response, not in the `Guid` field. This change checks the `guid` field
as a fallback if the `Guid` field is not set.

Owen D'Aprile (Mar 10, 2026, 8:24 PM EDT) ebc0aa8a d10ecb88

+32 -10
+32 -10
src/backend/sources/PlexApiSource.ts
··· 36 36 37 37 export const LOCAL_USER = 'PLEX_LOCAL_USER'; 38 38 39 + const MBID_PREFIX = "mbid://"; 40 + 39 41 const THUMB_REGEX = new RegExp(/\/library\/metadata\/(?<ratingkey>\d+)\/thumb\/\d+/) 40 42 41 43 export default class PlexApiSource extends MemoryPositionalSource { ··· 533 535 getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => new PlexPlayerState(logger, id, opts); 534 536 535 537 getMusicBrainzId = async (ratingKey: string | undefined): Promise<string | undefined> => { 536 - if (!ratingKey) { 537 - return null; 538 + if (ratingKey === undefined) { 539 + return undefined; 538 540 } 539 541 540 542 const cachedMbId = await this.mbIdCache.get(ratingKey); ··· 565 567 ); 566 568 567 569 const result = await request.json(); 570 + 571 + let mbid: string | null = null; 568 572 569 573 // There shouldn't be multiple metadata or GUID objects, but we return 570 574 // the first MBID to be safe. 571 - for (const metadata of result.MediaContainer.Metadata ?? []) { 572 - for (const guid of metadata.Guid ?? []) { 573 - if (typeof guid.id === "string" && guid.id.startsWith("mbid://")) { 574 - const mbid = guid.id.replace("mbid://", ""); 575 - 576 - await this.mbIdCache.set(ratingKey, mbid); 577 - return mbid; 575 + metadataLoop: for (const metadata of result?.MediaContainer?.Metadata ?? []) { 576 + this.logger.trace(`Guid: '${metadata.Guid?.map(g => g.id)?.join(", ")}', guid: '${metadata.guid}'`) 577 + 578 + if (Array.isArray(metadata.Guid)) { 579 + for (const guid of metadata.Guid) { 580 + if (typeof guid.id === "string" && guid.id.startsWith(MBID_PREFIX)) { 581 + mbid = guid.id.replace(MBID_PREFIX, ""); 582 + break metadataLoop; 583 + } 578 584 } 579 585 } 586 + 587 + // Some matched items store the MBID in the `MediaContainer.Metadata.guid` field, 588 + // so we check that as a fallback if there are no objects in the `Guid` field. 589 + if (typeof metadata.guid === "string" && metadata.guid.startsWith(MBID_PREFIX)) { 590 + mbid = metadata.guid.replace(MBID_PREFIX, ""); 591 + break metadataLoop; 592 + } 580 593 } 594 + 595 + this.logger.trace(`Extracted MBID: '${mbid}'`); 596 + 597 + await this.mbIdCache.set(ratingKey, mbid); 598 + 599 + if (mbid === null) { 600 + return undefined 601 + } 602 + 603 + return mbid; 581 604 } catch (e) { 582 605 this.logger.warn(new Error(`Failed to get MusicBrainz IDs from Plex for item ${ratingKey}`, {cause: e})); 583 606 } 584 607 585 - this.mbIdCache.set(ratingKey, null); 586 608 return undefined; 587 609 } 588 610 }