diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index fbbd4eb4..3c27da0f 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -204,61 +204,6 @@ export default class LastfmApiClient extends AbstractApiClient { throw e; } } - - public playToClientPayload(playObj: PlayObject): TrackScrobblePayload { - const { - data: { - artists = [], - album, - albumArtists = [], - track, - duration, - playDate, - meta: { - brainz: { - track: mbid - } = {}, - } = {} - } = {} - } = playObj; - - // LFM does not support multiple artists in scrobble payload - // https://www.last.fm/api/show/track.scrobble - let artist: string; - if (artists.length === 0) { - artist = ""; - } else { - artist = artists[0]; - } - - const additionalRichPayload: Partial = {}; - if(duration !== 0) { - additionalRichPayload.duration = duration; - } - - const rawPayload: TrackScrobblePayload = { - artist: artist, - track, - album, - timestamp: getScrobbleTsSOCDate(playObj).unix(), - mbid, - ...additionalRichPayload - }; - - // LFM does not support multiple artists in scrobble payload - // https://www.last.fm/api/show/track.scrobble - if (albumArtists.length > 0) { - rawPayload.albumArtist = albumArtists[0]; - } - - // I don't know if its lastfm-node-client building the request params incorrectly - // or the last.fm api not handling the params correctly... - // - // ...but in either case if any of the below properties is undefined (possibly also null??) - // then last.fm responds with an IGNORED scrobble and error code 1 (totally unhelpful) - // so remove all undefined keys from the object before passing to the api client - return removeUndefinedKeys(rawPayload); - } } export const scrobblePayloadToPlay = (obj: LastfmTrackUpdateRequest): PlayObject => { @@ -316,4 +261,62 @@ export const scrobblePayloadToPlay = (obj: LastfmTrackUpdateRequest): PlayObject } return play; -} \ No newline at end of file +} + +export const playToClientPayload = (playObj: PlayObject): TrackScrobblePayload => { + const { + data: { + artists = [], + album, + albumArtists = [], + track, + duration, + playDate, + meta: { + brainz: { + track: mbid + } = {}, + } = {} + } = {} + } = playObj; + + // LFM does not support multiple artists in scrobble payload + // https://www.last.fm/api/show/track.scrobble + let artist: string; + if (artists.length === 0) { + artist = ""; + } else { + artist = artists[0]; + } + + const additionalRichPayload: Partial = {}; + if(duration !== 0) { + additionalRichPayload.duration = duration; + } + + const rawPayload: TrackScrobblePayload = { + artist: artist, + track, + album, + timestamp: getScrobbleTsSOCDate(playObj).unix(), + mbid, + ...additionalRichPayload + }; + + // LFM ignores scrobbles where album artist is VA + // https://github.com/FoxxMD/multi-scrobbler/issues/340#issuecomment-3220774257 + const nonVaAlbumArtists = albumArtists.filter(x => x.trim().toLocaleLowerCase() !== 'va'); + // LFM does not support multiple artists in scrobble payload + // https://www.last.fm/api/show/track.scrobble + if (nonVaAlbumArtists.length > 0) { + rawPayload.albumArtist = nonVaAlbumArtists[0]; + } + + // I don't know if its lastfm-node-client building the request params incorrectly + // or the last.fm api not handling the params correctly... + // + // ...but in either case if any of the below properties is undefined (possibly also null??) + // then last.fm responds with an IGNORED scrobble and error code 1 (totally unhelpful) + // so remove all undefined keys from the object before passing to the api client + return removeUndefinedKeys(rawPayload); + } \ No newline at end of file diff --git a/src/backend/scrobblers/LastfmScrobbler.ts b/src/backend/scrobblers/LastfmScrobbler.ts index f08c9ce9..5e5b6307 100644 --- a/src/backend/scrobblers/LastfmScrobbler.ts +++ b/src/backend/scrobblers/LastfmScrobbler.ts @@ -7,7 +7,7 @@ import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; import { UpstreamError } from "../common/errors/UpstreamError.js"; import { FormatPlayObjectOptions } from "../common/infrastructure/Atomic.js"; import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; -import LastfmApiClient from "../common/vendor/LastfmApiClient.js"; +import LastfmApiClient, { playToClientPayload } from "../common/vendor/LastfmApiClient.js"; import { Notifiers } from "../notifier/Notifiers.js"; import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; @@ -103,7 +103,7 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { alreadyScrobbled = async (playObj: PlayObject, log = false) => (await this.existingScrobble(playObj)) !== undefined public playToClientPayload(playObject: PlayObject): object { - return this.api.playToClientPayload(playObject); + return playToClientPayload(playObject); } doScrobble = async (playObj: PlayObject) => { @@ -116,7 +116,7 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { const sType = newFromSource ? 'New' : 'Backlog'; - const scrobblePayload = this.api.playToClientPayload(playObj); + const scrobblePayload = playToClientPayload(playObj); try { const response = await this.api.callApi((client: any) => client.trackScrobble( @@ -173,7 +173,7 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { doPlayingNow = async (data: PlayObject) => { try { - const {timestamp, mbid, ...rest} = this.api.playToClientPayload(data); + const {timestamp, mbid, ...rest} = playToClientPayload(data); const response = await this.api.callApi((client: any) => client.trackUpdateNowPlaying(rest)); const { nowplaying: { diff --git a/src/backend/tests/lastfm/lastfm.test.ts b/src/backend/tests/lastfm/lastfm.test.ts new file mode 100644 index 00000000..f8fb41ad --- /dev/null +++ b/src/backend/tests/lastfm/lastfm.test.ts @@ -0,0 +1,19 @@ +import chai, { expect } from 'chai'; +import asPromised from 'chai-as-promised'; +import { after, before, describe, it } from 'mocha'; +import { generatePlay } from "../utils/PlayTestUtils.js"; + +import { playToClientPayload } from '../../common/vendor/LastfmApiClient.js'; + +chai.use(asPromised); + +describe('#LFM Scrobble Payload Behavior', function () { + + it('Should remove VA from album artist', function() { + const play = generatePlay({albumArtists: ['VA']}); + expect(playToClientPayload(play).albumArtist).to.be.undefined; + + const okPlay = generatePlay({albumArtists: ['My Dude']}); + expect(playToClientPayload(okPlay).albumArtist).eq('My Dude'); + }); +}); \ No newline at end of file