[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(rocksky): Use duration_ms instead of duation

FoxxMD (Nov 17, 2025, 3:49 PM UTC) 88d3e13b 7bf70d19

+12 -74
+12 -74
src/backend/common/vendor/RockSkyApiClient.ts
··· 173 173 submitListen = async (play: PlayObject, options: SubmitOptions = {}) => { 174 174 const { log = false, listenType = 'single'} = options; 175 175 try { 176 - const listenPayload: SubmitPayload = {listen_type: listenType, payload: [playToListenPayload(play)]}; 176 + 177 + const listenPayload = playToListenPayload(play); 177 178 if(listenType === 'playing_now') { 178 - delete listenPayload.payload[0].listened_at; 179 + delete listenPayload.listened_at; 180 + } 181 + // https://tangled.org/rocksky.app/rocksky/blob/main/crates/scrobbler/src/listenbrainz/types.rs#L11 182 + // rocksky only uses duration_ms 183 + if(play.data.duration !== undefined && listenPayload.track_metadata.additional_info?.duration !== undefined) { 184 + delete listenPayload.track_metadata.additional_info.duration; 185 + listenPayload.track_metadata.additional_info.duration_ms = play.data.duration * 1000; 179 186 } 187 + const submitPayload: SubmitPayload = {listen_type: listenType, payload: [listenPayload]}; 180 188 if(log) { 181 - this.logger.debug(`Submit Payload: ${JSON.stringify(listenPayload)}`); 189 + this.logger.debug(`Submit Payload: ${JSON.stringify(submitPayload)}`); 182 190 } 183 - // response consists of {"status": "ok"} 184 - // so no useful information 185 - // https://listenbrainz.readthedocs.io/en/latest/users/api-usage.html#submitting-listens 186 - // TODO may we should make a call to recent-listens to get the parsed scrobble? 187 - const resp = await this.callLZApi(request.post(`${joinedUrl(this.lzUrl.url,'1/submit-listens')}`).type('json').send(listenPayload)); 191 + const resp = await this.callLZApi(request.post(`${joinedUrl(this.lzUrl.url,'1/submit-listens')}`).type('json').send(submitPayload)); 188 192 if(log) { 189 193 this.logger.debug(`Submit Response: ${resp.text}`) 190 194 } ··· 192 196 } catch (e) { 193 197 throw e; 194 198 } 195 - } 196 - 197 - static listenPayloadToPlay(payload: ListenPayload, nowPlaying: boolean = false): PlayObject { 198 - const { 199 - listened_at = dayjs().unix(), 200 - track_metadata: { 201 - artist_name, 202 - track_name, 203 - release_name, 204 - additional_info: { 205 - duration, 206 - track_mbid, 207 - artist_mbids, 208 - artist_names = [], 209 - release_mbid, 210 - release_group_mbid, 211 - release_artist_name, 212 - release_artist_names = [] 213 - } = {} 214 - } = {}, 215 - } = payload; 216 - 217 - let albumArtists: string[]; 218 - if(release_artist_name !== undefined) { 219 - albumArtists = [release_artist_name]; 220 - } 221 - if(release_artist_names.length > 0) { 222 - albumArtists = unique([...(albumArtists ?? []), ...release_artist_names]) 223 - } 224 - 225 - return { 226 - data: { 227 - playDate: typeof listened_at === 'number' ? dayjs.unix(listened_at) : dayjs(listened_at), 228 - track: track_name, 229 - artists: unique([artist_name, ...artist_names]), 230 - albumArtists, 231 - album: release_name, 232 - duration, 233 - meta: { 234 - brainz: { 235 - artist: artist_mbids !== undefined ? artist_mbids : undefined, 236 - album: release_mbid, 237 - albumArtist: release_group_mbid, 238 - track: track_mbid 239 - } 240 - } 241 - }, 242 - meta: { 243 - nowPlaying, 244 - } 245 - } 246 - } 247 - 248 - static submitToPlayObj(submitObj: SubmitPayload, playObj: PlayObject): PlayObject { 249 - if (submitObj.payload.length > 0) { 250 - const respPlay = { 251 - ...playObj, 252 - }; 253 - respPlay.data = { 254 - ...playObj.data, 255 - album: submitObj.payload[0].track_metadata?.release_name ?? playObj.data.album, 256 - track: submitObj.payload[0].track_metadata?.track_name ?? playObj.data.album, 257 - }; 258 - return respPlay; 259 - } 260 - return playObj; 261 199 } 262 200 263 201 static formatPlayObj(obj: any, options: FormatPlayObjectOptions): PlayObject {