From c346c7cb315c7fd134a59047f4e92ddbcc54f25c Mon Sep 17 00:00:00 2001 From: Gabe Farrell <90876006+gabehf@users.noreply.github.com> Date: Thu, 1 Jan 2026 02:40:27 -0500 Subject: [PATCH] fix: associate tracks with release when scrobbling (#118) --- db/queries/track.sql | 7 +- ...ing_History_Audio_spotify_import_test.json | 977 ------------------ .../listenbrainz_shoko1_1749780844.zip | Bin 5785 -> 0 bytes .../import_complete/maloja_import_test.json | 771 -------------- .../recenttracks-shoko2-1749776100.json | 1 - engine/import_test.go | 26 +- internal/catalog/associate_track.go | 16 +- internal/db/opts.go | 1 + internal/db/psql/track.go | 13 +- internal/db/psql/track_test.go | 21 +- internal/importer/koito.go | 1 + internal/repository/track.sql.go | 18 +- 12 files changed, 58 insertions(+), 1794 deletions(-) delete mode 100755 engine/Vkhuk8cw/import_complete/Streaming_History_Audio_spotify_import_test.json delete mode 100755 engine/Vkhuk8cw/import_complete/listenbrainz_shoko1_1749780844.zip delete mode 100755 engine/Vkhuk8cw/import_complete/maloja_import_test.json delete mode 100755 engine/Vkhuk8cw/import_complete/recenttracks-shoko2-1749776100.json diff --git a/db/queries/track.sql b/db/queries/track.sql index a9fc425..af7006a 100644 --- a/db/queries/track.sql +++ b/db/queries/track.sql @@ -27,14 +27,15 @@ FROM tracks_with_title t JOIN artist_tracks at ON t.id = at.track_id WHERE at.artist_id = $1; --- name: GetTrackByTitleAndArtists :one +-- name: GetTrackByTrackInfo :one SELECT t.* FROM tracks_with_title t JOIN artist_tracks at ON at.track_id = t.id WHERE t.title = $1 - AND at.artist_id = ANY($2::int[]) + AND at.artist_id = ANY($3::int[]) + AND t.release_id = $2 GROUP BY t.id, t.title, t.musicbrainz_id, t.duration, t.release_id -HAVING COUNT(DISTINCT at.artist_id) = cardinality($2::int[]); +HAVING COUNT(DISTINCT at.artist_id) = cardinality($3::int[]); -- name: GetTopTracksPaginated :many SELECT diff --git a/engine/Vkhuk8cw/import_complete/Streaming_History_Audio_spotify_import_test.json b/engine/Vkhuk8cw/import_complete/Streaming_History_Audio_spotify_import_test.json deleted file mode 100755 index 71186e4..0000000 --- a/engine/Vkhuk8cw/import_complete/Streaming_History_Audio_spotify_import_test.json +++ /dev/null @@ -1,977 +0,0 @@ -[ - { - "ts": "2025-04-28T21:04:35Z", - "platform": "windows", - "ms_played": 1603, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "only my railgun", - "master_metadata_album_artist_name": "fripSide", - "master_metadata_album_album_name": "infinite synthesis", - "spotify_track_uri": "spotify:track:3aJ2aJz5xL03hpaqdPS7Ah", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745874272, - "incognito_mode": false - }, - { - "ts": "2025-04-28T21:04:49Z", - "platform": "windows", - "ms_played": 10953, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "LEVEL5-judgelight-", - "master_metadata_album_artist_name": "fripSide", - "master_metadata_album_album_name": "infinite synthesis", - "spotify_track_uri": "spotify:track:0hjif67e3mBkrPIlRDXHLS", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745874274, - "incognito_mode": false - }, - { - "ts": "2025-04-28T21:16:38Z", - "platform": "windows", - "ms_played": 93283, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Red Liberation", - "master_metadata_album_artist_name": "fripSide", - "master_metadata_album_album_name": "infinite Resonance 2", - "spotify_track_uri": "spotify:track:2B8geqnq9YIym0ixYn83Pd", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "logout", - "shuffle": false, - "skipped": false, - "offline": false, - "offline_timestamp": 1745874288, - "incognito_mode": false - }, - { - "ts": "2025-04-28T22:29:29Z", - "platform": "android", - "ms_played": 9640, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "New Genesis", - "master_metadata_album_artist_name": "Ado", - "master_metadata_album_album_name": "UTA'S SONGS ONE PIECE FILM RED", - "spotify_track_uri": "spotify:track:6A8NfypDHuwiLWbo4a1yca", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "logout", - "shuffle": true, - "skipped": false, - "offline": false, - "offline_timestamp": 1745878757, - "incognito_mode": false - }, - { - "ts": "2025-04-29T00:37:09Z", - "platform": "windows", - "ms_played": 181028, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Clairvoyant", - "master_metadata_album_artist_name": "The Story So Far", - "master_metadata_album_album_name": "The Story So Far / Stick To Your Guns Split", - "spotify_track_uri": "spotify:track:5fgnsSQYKIlEn2KTQcGjh2", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "playbtn", - "reason_end": "trackdone", - "shuffle": false, - "skipped": false, - "offline": false, - "offline_timestamp": 1745886847, - "incognito_mode": false - }, - { - "ts": "2025-04-29T00:59:36Z", - "platform": "windows", - "ms_played": 824, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Clairvoyant", - "master_metadata_album_artist_name": "The Story So Far", - "master_metadata_album_album_name": "The Story So Far / Stick To Your Guns Split", - "spotify_track_uri": "spotify:track:5fgnsSQYKIlEn2KTQcGjh2", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "trackdone", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745887028, - "incognito_mode": false - }, - { - "ts": "2025-04-29T01:15:07Z", - "platform": "windows", - "ms_played": 4443, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "比翼の羽根", - "master_metadata_album_artist_name": "eufonius", - "master_metadata_album_album_name": "比翼の羽根", - "spotify_track_uri": "spotify:track:6FFshKmfm9h5MBpnsRO73c", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": true, - "skipped": true, - "offline": false, - "offline_timestamp": 1745888374, - "incognito_mode": false - }, - { - "ts": "2025-04-29T01:16:02Z", - "platform": "windows", - "ms_played": 36093, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "ファジーネーブル", - "master_metadata_album_artist_name": "Conton Candy", - "master_metadata_album_album_name": "melt pop", - "spotify_track_uri": "spotify:track:3FniX6mJvTQWruKp5PDexD", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745889306, - "incognito_mode": false - }, - { - "ts": "2025-04-29T01:25:35Z", - "platform": "windows", - "ms_played": 54615, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Bad Spanish", - "master_metadata_album_artist_name": "Nervous Dater", - "master_metadata_album_album_name": "Don't Be a Stranger", - "spotify_track_uri": "spotify:track:793ILNfrm9dQyp3k0P53HG", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745889361, - "incognito_mode": false - }, - { - "ts": "2025-04-29T01:28:08Z", - "platform": "windows", - "ms_played": 153153, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Fight Song", - "master_metadata_album_artist_name": "Rachel Platten", - "master_metadata_album_album_name": "Wildfire", - "spotify_track_uri": "spotify:track:37f4ITSlgPX81ad2EvmVQr", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745889934, - "incognito_mode": false - }, - { - "ts": "2025-04-29T01:29:19Z", - "platform": "windows", - "ms_played": 70473, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Cake By The Ocean", - "master_metadata_album_artist_name": "DNCE", - "master_metadata_album_album_name": "DNCE", - "spotify_track_uri": "spotify:track:76hfruVvmfQbw0eYn1nmeC", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745890087, - "incognito_mode": false - }, - { - "ts": "2025-04-29T01:31:08Z", - "platform": "windows", - "ms_played": 108465, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "The Sweet Escape", - "master_metadata_album_artist_name": "Gwen Stefani", - "master_metadata_album_album_name": "The Sweet Escape", - "spotify_track_uri": "spotify:track:66ZcOcouenzZEnzTJvoFmH", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745890158, - "incognito_mode": false - }, - { - "ts": "2025-04-29T01:33:19Z", - "platform": "windows", - "ms_played": 130353, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "two", - "master_metadata_album_artist_name": "bbno$", - "master_metadata_album_album_name": "two", - "spotify_track_uri": "spotify:track:6DRZmJa38MaMNthwG3fCBD", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745890267, - "incognito_mode": false - }, - { - "ts": "2025-04-29T01:37:48Z", - "platform": "windows", - "ms_played": 35993, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "C’est La Vie (with bbno$ & Rich Brian)", - "master_metadata_album_artist_name": "Yung Gravy", - "master_metadata_album_album_name": "Marvelous", - "spotify_track_uri": "spotify:track:3QqOcLtTU8zzlQRJCZzttP", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745890398, - "incognito_mode": false - }, - { - "ts": "2025-04-29T02:23:28Z", - "platform": "windows", - "ms_played": 22337, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Metal", - "master_metadata_album_artist_name": "The Beths", - "master_metadata_album_album_name": "Metal", - "spotify_track_uri": "spotify:track:6KF6TkyYpEWKg6BZ3OYJz7", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "logout", - "shuffle": false, - "skipped": false, - "offline": false, - "offline_timestamp": 1745890667, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:14:32Z", - "platform": "windows", - "ms_played": 21414, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "SHINUNA!", - "master_metadata_album_artist_name": "Kocchi no Kento", - "master_metadata_album_album_name": "SHINUNA!", - "spotify_track_uri": "spotify:track:5QSo4Jbok8O9EgeDkumK9q", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "logout", - "shuffle": false, - "skipped": false, - "offline": false, - "offline_timestamp": 1745905178, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:36:54Z", - "platform": "windows", - "ms_played": 53063, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "I'm getting on the bus to the other world, see ya!", - "master_metadata_album_artist_name": "TUYU", - "master_metadata_album_album_name": "It's Raining After All", - "spotify_track_uri": "spotify:track:3rCJptQKkXrTx6qUXqz7dD", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745947869, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:37:41Z", - "platform": "windows", - "ms_played": 28193, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Harukaze", - "master_metadata_album_artist_name": "Matsuri", - "master_metadata_album_album_name": "Harukaze", - "spotify_track_uri": "spotify:track:21Jj7td1D5HQYr18MLZTLS", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745948214, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:38:16Z", - "platform": "windows", - "ms_played": 10763, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Otona no Jijo", - "master_metadata_album_artist_name": "Za Ninngenn", - "master_metadata_album_album_name": "Sanman", - "spotify_track_uri": "spotify:track:6BfDkvp3wJq7cA0xDWDHAI", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745948260, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:39:06Z", - "platform": "windows", - "ms_played": 4393, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Mela!", - "master_metadata_album_artist_name": "Ryokuoushoku Shakai", - "master_metadata_album_album_name": "Mela!", - "spotify_track_uri": "spotify:track:6IO5nn84TKArsi3cjpIqaD", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745948296, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:41:01Z", - "platform": "windows", - "ms_played": 12676, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "moved along", - "master_metadata_album_artist_name": "wilt", - "master_metadata_album_album_name": "moved along", - "spotify_track_uri": "spotify:track:3CZZnpgvHNR71M4QnkQjzl", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": true, - "skipped": true, - "offline": false, - "offline_timestamp": 1745948346, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:42:44Z", - "platform": "windows", - "ms_played": 33263, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Air Guitar", - "master_metadata_album_artist_name": "Sobs", - "master_metadata_album_album_name": "Air Guitar", - "spotify_track_uri": "spotify:track:1ZL73Fic49PdXUSvL69wh8", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": true, - "skipped": true, - "offline": false, - "offline_timestamp": 1745948460, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:42:56Z", - "platform": "windows", - "ms_played": 9943, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Pharmacist", - "master_metadata_album_artist_name": "Alvvays", - "master_metadata_album_album_name": "Blue Rev", - "spotify_track_uri": "spotify:track:3r2vyNnqFKr6IraCqLtoBI", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": true, - "skipped": true, - "offline": false, - "offline_timestamp": 1745948563, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:44:41Z", - "platform": "windows", - "ms_played": 28573, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "freequent letdown", - "master_metadata_album_artist_name": "illuminati hotties", - "master_metadata_album_album_name": "FREE I.H: This Is Not the One You've Been Waiting For", - "spotify_track_uri": "spotify:track:5ZJfOkp2r5AbLjRdnu3UQd", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": true, - "skipped": true, - "offline": false, - "offline_timestamp": 1745948576, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:46:12Z", - "platform": "windows", - "ms_played": 6041, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Choke", - "master_metadata_album_artist_name": "I DONT KNOW HOW BUT THEY FOUND ME", - "master_metadata_album_album_name": "Choke", - "spotify_track_uri": "spotify:track:37mfTcSlX60JtAvAETytGs", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745948680, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:47:27Z", - "platform": "windows", - "ms_played": 28523, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "We Own the Night", - "master_metadata_album_artist_name": "Dance Gavin Dance", - "master_metadata_album_album_name": "Instant Gratification", - "spotify_track_uri": "spotify:track:7xCcUcMcGsIYGKUVgBZUw5", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745948771, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:52:13Z", - "platform": "windows", - "ms_played": 72901, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "鏡面の波", - "master_metadata_album_artist_name": "YURiKA", - "master_metadata_album_album_name": "鏡面の波", - "spotify_track_uri": "spotify:track:17pYAFEZjKZFU5PHiUMzqx", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745948847, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:52:34Z", - "platform": "windows", - "ms_played": 15443, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "パレイド", - "master_metadata_album_artist_name": "syh", - "master_metadata_album_album_name": "パレイド", - "spotify_track_uri": "spotify:track:7uXzW6dPhkd4NbRv8sLNS6", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745949133, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:53:33Z", - "platform": "windows", - "ms_played": 40213, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Burning Friday Night", - "master_metadata_album_artist_name": "Lucky Kilimanjaro", - "master_metadata_album_album_name": "FULLCOLOR", - "spotify_track_uri": "spotify:track:1NlkoYEA1ndLQIKzXTPh9V", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745949154, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:53:45Z", - "platform": "windows", - "ms_played": 11946, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Better Things", - "master_metadata_album_artist_name": "aespa", - "master_metadata_album_album_name": "Drama - The 4th Mini Album", - "spotify_track_uri": "spotify:track:330IIz7d75eqAsKq1xhzXR", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745949212, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:53:57Z", - "platform": "windows", - "ms_played": 9953, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Thirsty", - "master_metadata_album_artist_name": "aespa", - "master_metadata_album_album_name": "MY WORLD - The 3rd Mini Album", - "spotify_track_uri": "spotify:track:6nICBdDevG4NZysIqDFPEa", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745949225, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:54:47Z", - "platform": "windows", - "ms_played": 44470, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Lucid Dream", - "master_metadata_album_artist_name": "aespa", - "master_metadata_album_album_name": "Savage - The 1st Mini Album", - "spotify_track_uri": "spotify:track:285Bh5EkbxGGE76ge8JDbH", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745949237, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:55:59Z", - "platform": "windows", - "ms_played": 70353, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Girls Never Die", - "master_metadata_album_artist_name": "tripleS", - "master_metadata_album_album_name": "", - "spotify_track_uri": "spotify:track:45OflED18VsURGw2z0Y6Cv", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745949286, - "incognito_mode": false - }, - { - "ts": "2025-04-29T17:58:34Z", - "platform": "windows", - "ms_played": 8546, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Midas Touch", - "master_metadata_album_artist_name": "KISS OF LIFE", - "master_metadata_album_album_name": "Midas Touch", - "spotify_track_uri": "spotify:track:0vaxYDAuAO1nPolC6bQp7V", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745949358, - "incognito_mode": false - }, - { - "ts": "2025-04-29T19:59:23Z", - "platform": "windows", - "ms_played": 3033, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "I Will Go To You Like the First Snow", - "master_metadata_album_artist_name": "AILEE", - "master_metadata_album_album_name": "Guardian (Original Television Soundtrack), Pt. 9", - "spotify_track_uri": "spotify:track:2BPXILn0MqOe5WroVXlvN1", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "logout", - "shuffle": false, - "skipped": false, - "offline": false, - "offline_timestamp": 1745949513, - "incognito_mode": false - }, - { - "ts": "2025-04-29T21:55:06Z", - "platform": "windows", - "ms_played": 45964, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Ready or Not", - "master_metadata_album_artist_name": "Bridgit Mendler", - "master_metadata_album_album_name": "Hello My Name Is...", - "spotify_track_uri": "spotify:track:5xvUgoVED1F4mBu8FL0HaW", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745963659, - "incognito_mode": false - }, - { - "ts": "2025-04-29T22:04:53Z", - "platform": "windows", - "ms_played": 44523, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "お勉強しといてよ", - "master_metadata_album_artist_name": "ZUTOMAYO", - "master_metadata_album_album_name": "お勉強しといてよ", - "spotify_track_uri": "spotify:track:6k90ibcH1z8Mx9684nfuLW", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745963706, - "incognito_mode": false - }, - { - "ts": "2025-04-29T22:06:52Z", - "platform": "windows", - "ms_played": 8893, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "Finale.", - "master_metadata_album_artist_name": "eill", - "master_metadata_album_album_name": "my dream box", - "spotify_track_uri": "spotify:track:2uGJ89l8tciHkYxzJF3xv6", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "endplay", - "shuffle": false, - "skipped": true, - "offline": false, - "offline_timestamp": 1745964293, - "incognito_mode": false - }, - { - "ts": "2025-04-29T23:12:45Z", - "platform": "windows", - "ms_played": 5883, - "conn_country": "US", - "ip_addr": "x.x.x.x", - "master_metadata_track_name": "ただ君に晴れ", - "master_metadata_album_artist_name": "ヨルシカ", - "master_metadata_album_album_name": "負け犬にアンコールはいらない", - "spotify_track_uri": "spotify:track:3wJHCry960drNlAUGrJLmz", - "episode_name": null, - "episode_show_name": null, - "spotify_episode_uri": null, - "audiobook_title": null, - "audiobook_uri": null, - "audiobook_chapter_uri": null, - "audiobook_chapter_title": null, - "reason_start": "clickrow", - "reason_end": "logout", - "shuffle": false, - "skipped": false, - "offline": false, - "offline_timestamp": 1745964412, - "incognito_mode": false - } -] \ No newline at end of file diff --git a/engine/Vkhuk8cw/import_complete/listenbrainz_shoko1_1749780844.zip b/engine/Vkhuk8cw/import_complete/listenbrainz_shoko1_1749780844.zip deleted file mode 100755 index be96a22bc7aa4ab986c8a547de9e2c9620cc6600..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5785 zcmds*&5K-B7Qow2FgqdYN_f=5Rc>;>Ke7{L2*!^PA^}B;bH9>js_Hebs*s+h7ws?` zXEiXhpMf~5VVGeyuHCutuLu#{x^e5d)m@+c$}Vk$Xf;Ju*L(M!bI-@`oO5s8eCFB9 zd*$`zi(h{*edUK&U&HU^{Q|zHm6OXwf3;1hUCv#@9II>YAQCc5v9MNBH1LihY>$u2;-RXDroB zTy=^|X|GhhlXYkA;CN}$LU_<0rU^ut0Iokg{pm!rs`q9pPw(Na&`wZ(e}3V;X?wf; zJoI(!4`F}59^IR*4*;`mRzfMt3|HK-#qu%U^5Nn9S!FH$(3750EommgCrzZtmN+jY z$=Na=trMI=k@ay?kNW%fTNtC&s@{rLp-r*gQ10lXh1h|PCXjET9{sWUB>59( zorK4|wwdlPU9F_lDQ9AIvP4EpiFZ5^9%)cUB@5Q{j*gBlZOL2X%vCOh zvVC(KJ&gG?j4?JzjZ%;-IU-|(Cq9Wxm`l!ys12&qA$+^kGgj*6&90l?Nxv>GNdt&T zZHb|pV#skv9K|gfsYT4*q{vPKc9ToW3-Cb#(b{?5Lgd&3N6D%#?&1#W*@#8Q>vDX*}0#H5kPQ3gh2&`2)h z&<2uwp96OZW>|*vd1Ww^J9+gx@Zkly7MxLB8$|%wL?jiAcorQIfWaq0f->|%Tw_!_ zjDmBy_Wmd|!`!qJ@QAK=r^8unZ}cXw4^vwQ7*&60nu&+Opvs7~8*ng42i4trFsK15 z6pGtUH7xQB>mE|Akoe^J-Bgpwdj(No)-gmrHIdlBB?(ke#=tuWx&tgTmA2F6%oaMK z=A~CC9IJDwIhbM;<6c=}MWm4l1={fr+1tf#+NhgpyKZ?W^}3iAumQ0=aAI=lm@LO8 z!~-MT2j`;nnhRGpv4VrsR+2?em6h0GX$(&poFdi>L9{fETJ(qGTY9bT$m(L+PTF-b z?W|MR_b_k4d_*8iNvu^^CqphY3zUSYg-FJ8dE(U_kfx|)C9h5sWTg$>F>0S+7hVu) zi8+K$@>nP_fnEnUKY#JfzyI<0zkhxF`Ty5t&l?%6 zpgvQF)m33zDk)YJ2o{S82h(>yq6s@JNwVcO!z zb)#wPy4=1!yGPT(VByW~w9YXB#s|)w6a!s4kDZ!VlyGcel+woIv{i&`q&2FsqUlCv zuPNNweo&slVB8WLMZmKHj9Xubs)NJR11*$>u7Pg5$TgdE^JLeZ?xb4>cTSDHWJB>w zIRwZlChJWg+G2MnwL+l8h@&KqF~Y}GT8px?<;uC|mm$%Clp?PS80*$wd+wYV*dZmW zydiimWla*jLjmM6C>F}TByY7t{A#Q3$%-9zMMnpGglF>KgeiUgEG>btVF zB#nq0u>QFYxgc2-C>`(wk7SLJj;*gC)=@M%%@h@lcp4aHWVvPjF|i thsWh(aWcHQZ1dFjWYON 0 { - l.Debug().Msgf("Fetching track from DB with title '%s' and artist id(s) '%v'", opts.Title, opts.ArtistIDs) - t, err := d.q.GetTrackByTitleAndArtists(ctx, repository.GetTrackByTitleAndArtistsParams{ - Title: opts.Title, - Column2: opts.ArtistIDs, + } else if len(opts.ArtistIDs) > 0 && opts.ReleaseID != 0 { + l.Debug().Msgf("Fetching track from DB from release id %d with title '%s' and artist id(s) '%v'", opts.ReleaseID, opts.Title, opts.ArtistIDs) + t, err := d.q.GetTrackByTrackInfo(ctx, repository.GetTrackByTrackInfoParams{ + Title: opts.Title, + ReleaseID: opts.ReleaseID, + Column3: opts.ArtistIDs, }) if err != nil { - return nil, fmt.Errorf("GetTrack: GetTrackByTitleAndArtists: %w", err) + return nil, fmt.Errorf("GetTrack: GetTrackByTrackInfo: %w", err) } track = models.Track{ ID: t.ID, diff --git a/internal/db/psql/track_test.go b/internal/db/psql/track_test.go index 777b22c..7fa58d4 100644 --- a/internal/db/psql/track_test.go +++ b/internal/db/psql/track_test.go @@ -16,55 +16,55 @@ func testDataForTracks(t *testing.T) { // Insert artists err := store.Exec(context.Background(), - `INSERT INTO artists (musicbrainz_id) + `INSERT INTO artists (musicbrainz_id) VALUES ('00000000-0000-0000-0000-000000000001'), ('00000000-0000-0000-0000-000000000002')`) require.NoError(t, err) // Insert artist aliases err = store.Exec(context.Background(), - `INSERT INTO artist_aliases (artist_id, alias, source, is_primary) + `INSERT INTO artist_aliases (artist_id, alias, source, is_primary) VALUES (1, 'Artist One', 'Testing', true), (2, 'Artist Two', 'Testing', true)`) require.NoError(t, err) // Insert release groups err = store.Exec(context.Background(), - `INSERT INTO releases (musicbrainz_id) + `INSERT INTO releases (musicbrainz_id) VALUES ('00000000-0000-0000-0000-000000000011'), ('00000000-0000-0000-0000-000000000022')`) require.NoError(t, err) // Insert release aliases err = store.Exec(context.Background(), - `INSERT INTO release_aliases (release_id, alias, source, is_primary) + `INSERT INTO release_aliases (release_id, alias, source, is_primary) VALUES (1, 'Release Group One', 'Testing', true), (2, 'Release Group Two', 'Testing', true)`) require.NoError(t, err) // Insert tracks err = store.Exec(context.Background(), - `INSERT INTO tracks (musicbrainz_id, release_id, duration) + `INSERT INTO tracks (musicbrainz_id, release_id, duration) VALUES ('11111111-1111-1111-1111-111111111111', 1, 100), ('22222222-2222-2222-2222-222222222222', 2, 100)`) require.NoError(t, err) // Insert track aliases err = store.Exec(context.Background(), - `INSERT INTO track_aliases (track_id, alias, source, is_primary) + `INSERT INTO track_aliases (track_id, alias, source, is_primary) VALUES (1, 'Track One', 'Testing', true), (2, 'Track Two', 'Testing', true)`) require.NoError(t, err) // Associate tracks with artists err = store.Exec(context.Background(), - `INSERT INTO artist_tracks (artist_id, track_id) + `INSERT INTO artist_tracks (artist_id, track_id) VALUES (1, 1), (2, 2)`) require.NoError(t, err) // Associate tracks with artists err = store.Exec(context.Background(), - `INSERT INTO listens (user_id, track_id, listened_at) + `INSERT INTO listens (user_id, track_id, listened_at) VALUES (1, 1, NOW()), (1, 2, NOW())`) require.NoError(t, err) } @@ -88,9 +88,10 @@ func TestGetTrack(t *testing.T) { assert.Equal(t, "Track Two", track.Title) assert.EqualValues(t, 100, track.TimeListened) - // Test GetTrack by Title and ArtistIDs + // Test GetTrack by Title, Release and ArtistIDs track, err = store.GetTrack(ctx, db.GetTrackOpts{ Title: "Track One", + ReleaseID: 1, ArtistIDs: []int32{1}, }) require.NoError(t, err) @@ -99,7 +100,7 @@ func TestGetTrack(t *testing.T) { assert.EqualValues(t, 100, track.TimeListened) // Test GetTrack with insufficient information - _, err = store.GetTrack(ctx, db.GetTrackOpts{}) + _, err = store.GetTrack(ctx, db.GetTrackOpts{Title: "Track One"}) assert.Error(t, err) } func TestSaveTrack(t *testing.T) { diff --git a/internal/importer/koito.go b/internal/importer/koito.go index ae74cbf..e120454 100644 --- a/internal/importer/koito.go +++ b/internal/importer/koito.go @@ -126,6 +126,7 @@ func ImportKoitoFile(ctx context.Context, store db.DB, filename string) error { track, err := store.GetTrack(ctx, db.GetTrackOpts{ MusicBrainzID: mbid, Title: getPrimaryAliasFromAliasSlice(data.Listens[i].Track.Aliases), + ReleaseID: albumId, ArtistIDs: artistIds, }) if errors.Is(err, pgx.ErrNoRows) { diff --git a/internal/repository/track.sql.go b/internal/repository/track.sql.go index c531210..883e13c 100644 --- a/internal/repository/track.sql.go +++ b/internal/repository/track.sql.go @@ -417,23 +417,25 @@ func (q *Queries) GetTrackByMbzID(ctx context.Context, musicbrainzID *uuid.UUID) return i, err } -const getTrackByTitleAndArtists = `-- name: GetTrackByTitleAndArtists :one +const getTrackByTrackInfo = `-- name: GetTrackByTrackInfo :one SELECT t.id, t.musicbrainz_id, t.duration, t.release_id, t.title FROM tracks_with_title t JOIN artist_tracks at ON at.track_id = t.id WHERE t.title = $1 - AND at.artist_id = ANY($2::int[]) + AND at.artist_id = ANY($3::int[]) + AND t.release_id = $2 GROUP BY t.id, t.title, t.musicbrainz_id, t.duration, t.release_id -HAVING COUNT(DISTINCT at.artist_id) = cardinality($2::int[]) +HAVING COUNT(DISTINCT at.artist_id) = cardinality($3::int[]) ` -type GetTrackByTitleAndArtistsParams struct { - Title string - Column2 []int32 +type GetTrackByTrackInfoParams struct { + Title string + ReleaseID int32 + Column3 []int32 } -func (q *Queries) GetTrackByTitleAndArtists(ctx context.Context, arg GetTrackByTitleAndArtistsParams) (TracksWithTitle, error) { - row := q.db.QueryRow(ctx, getTrackByTitleAndArtists, arg.Title, arg.Column2) +func (q *Queries) GetTrackByTrackInfo(ctx context.Context, arg GetTrackByTrackInfoParams) (TracksWithTitle, error) { + row := q.db.QueryRow(ctx, getTrackByTrackInfo, arg.Title, arg.ReleaseID, arg.Column3) var i TracksWithTitle err := row.Scan( &i.ID,