From 9f97a080044e2ef5791165620e2aa5aa6a36051f Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Fri, 3 Oct 2025 20:00:16 +0300 Subject: [PATCH] fix: avoid duplicate scrobbles --- apps/api/src/nowplaying/nowplaying.service.ts | 106 +++++++++--------- 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/apps/api/src/nowplaying/nowplaying.service.ts b/apps/api/src/nowplaying/nowplaying.service.ts index 59a76a4c..998ecf01 100644 --- a/apps/api/src/nowplaying/nowplaying.service.ts +++ b/apps/api/src/nowplaying/nowplaying.service.ts @@ -13,7 +13,7 @@ import type { Track } from "types/track"; export async function putArtistRecord( track: Track, - agent: Agent, + agent: Agent ): Promise { const rkey = TID.nextStr(); const record: { @@ -54,7 +54,7 @@ export async function putArtistRecord( export async function putAlbumRecord( track: Track, - agent: Agent, + agent: Agent ): Promise { const rkey = TID.nextStr(); @@ -94,7 +94,7 @@ export async function putAlbumRecord( export async function putSongRecord( track: Track, - agent: Agent, + agent: Agent ): Promise { const rkey = TID.nextStr(); @@ -146,7 +146,7 @@ export async function putSongRecord( async function putScrobbleRecord( track: Track, - agent: Agent, + agent: Agent ): Promise { const rkey = TID.nextStr(); @@ -308,35 +308,33 @@ export async function scrobbleTrack( ctx: Context, track: Track, agent: Agent, - userDid: string, + userDid: string ): Promise { // check if scrobble already exists (user did + timestamp) - const scrobbleTime = dayjs.unix(track.timestamp); - if (track.timestamp) { - const existingScrobble = await ctx.client.db.scrobbles - .filter("user_id.did", equals(userDid)) - .filter("track_id.title", equals(track.title)) - .filter("track_id.artist", equals(track.artist)) - .filter({ - $any: [ - { - timestamp: { - $ge: scrobbleTime.subtract(5, "seconds").toISOString(), - }, + const scrobbleTime = dayjs.unix(track.timestamp || dayjs().unix()); + const existingScrobble = await ctx.client.db.scrobbles + .filter("user_id.did", equals(userDid)) + .filter("track_id.title", equals(track.title)) + .filter("track_id.artist", equals(track.artist)) + .filter({ + $any: [ + { + timestamp: { + $ge: scrobbleTime.subtract(10, "seconds").toISOString(), }, - { timestamp: { $le: scrobbleTime.add(5, "seconds").toISOString() } }, - ], - }) - .getFirst(); + }, + { timestamp: { $le: scrobbleTime.add(10, "seconds").toISOString() } }, + ], + }) + .getFirst(); - if (existingScrobble) { - console.log( - `Scrobble already exists for ${chalk.cyan(track.title)} at ${chalk.cyan( - dayjs.unix(track.timestamp).format("YYYY-MM-DD HH:mm:ss"), - )}`, - ); - return; - } + if (existingScrobble) { + console.log( + `Scrobble already exists for ${chalk.cyan(track.title)} at ${chalk.cyan( + dayjs.unix(track.timestamp).format("YYYY-MM-DD HH:mm:ss") + )}` + ); + return; } let existingTrack = await ctx.client.db.tracks @@ -345,10 +343,10 @@ export async function scrobbleTrack( equals( createHash("sha256") .update( - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), + `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() ) - .digest("hex"), - ), + .digest("hex") + ) ) .getFirst(); @@ -359,8 +357,8 @@ export async function scrobbleTrack( equals( createHash("sha256") .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) - .digest("hex"), - ), + .digest("hex") + ) ) .getFirst(); if (album) { @@ -377,8 +375,8 @@ export async function scrobbleTrack( equals( createHash("sha256") .update(track.albumArtist.toLowerCase()) - .digest("hex"), - ), + .digest("hex") + ) ) .getFirst(); if (artist) { @@ -405,8 +403,8 @@ export async function scrobbleTrack( equals( createHash("sha256") .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) - .digest("hex"), - ), + .digest("hex") + ) ) .getFirst(); @@ -419,10 +417,10 @@ export async function scrobbleTrack( equals( createHash("sha256") .update( - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), + `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() ) - .digest("hex"), - ), + .digest("hex") + ) ) .getFirst(); await new Promise((resolve) => setTimeout(resolve, 1000)); @@ -435,7 +433,7 @@ export async function scrobbleTrack( if (existingTrack) { console.log( - `Song found: ${chalk.cyan(existingTrack.xata_id)} - ${track.title}, after ${chalk.magenta(tries)} tries`, + `Song found: ${chalk.cyan(existingTrack.xata_id)} - ${track.title}, after ${chalk.magenta(tries)} tries` ); } @@ -485,10 +483,10 @@ export async function scrobbleTrack( equals( createHash("sha256") .update( - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), + `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() ) - .digest("hex"), - ), + .digest("hex") + ) ) .getFirst(); @@ -498,7 +496,7 @@ export async function scrobbleTrack( tries < 30 ) { console.log( - `Artist uri not ready, trying again: ${chalk.magenta(tries + 1)}`, + `Artist uri not ready, trying again: ${chalk.magenta(tries + 1)}` ); existingTrack = await ctx.client.db.tracks .filter( @@ -506,10 +504,10 @@ export async function scrobbleTrack( equals( createHash("sha256") .update( - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), + `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() ) - .digest("hex"), - ), + .digest("hex") + ) ) .getFirst(); @@ -521,8 +519,8 @@ export async function scrobbleTrack( equals( createHash("sha256") .update(track.albumArtist.toLowerCase()) - .digest("hex"), - ), + .digest("hex") + ) ) .getFirst(); if (artist) { @@ -541,8 +539,8 @@ export async function scrobbleTrack( equals( createHash("sha256") .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) - .digest("hex"), - ), + .digest("hex") + ) ) .getFirst(); if (album) { @@ -569,7 +567,7 @@ export async function scrobbleTrack( if (existingTrack?.artist_uri) { console.log( - `Artist uri ready: ${chalk.cyan(existingTrack.xata_id)} - ${track.title}, after ${chalk.magenta(tries)} tries`, + `Artist uri ready: ${chalk.cyan(existingTrack.xata_id)} - ${track.title}, after ${chalk.magenta(tries)} tries` ); } -- 2.51.2