mirror of
https://github.com/riwiwa/muzi.git
synced 2026-02-28 03:46:57 -08:00
fix spotify scrobbling
This commit is contained in:
26
db/db.go
26
db/db.go
@@ -18,7 +18,10 @@ func CreateAllTables() error {
|
|||||||
if err := CreateUsersTable(); err != nil {
|
if err := CreateUsersTable(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return CreateSessionsTable()
|
if err := CreateSessionsTable(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return CreateSpotifyLastTrackTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDbUrl(dbName bool) string {
|
func GetDbUrl(dbName bool) string {
|
||||||
@@ -134,7 +137,26 @@ func CleanupExpiredSessions() error {
|
|||||||
_, err := Pool.Exec(context.Background(),
|
_, err := Pool.Exec(context.Background(),
|
||||||
"DELETE FROM sessions WHERE expires_at < NOW();")
|
"DELETE FROM sessions WHERE expires_at < NOW();")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error cleaning up expired sessions: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error cleaning up sessions: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateSpotifyLastTrackTable() error {
|
||||||
|
_, err := Pool.Exec(context.Background(),
|
||||||
|
`CREATE TABLE IF NOT EXISTS spotify_last_track (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(pk) ON DELETE CASCADE,
|
||||||
|
track_id TEXT NOT NULL,
|
||||||
|
song_name TEXT NOT NULL,
|
||||||
|
artist TEXT NOT NULL,
|
||||||
|
album_name TEXT,
|
||||||
|
duration_ms INTEGER NOT NULL,
|
||||||
|
progress_ms INTEGER NOT NULL DEFAULT 0,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);`)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error creating spotify_last_track table: %v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package scrobble
|
package scrobble
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -10,6 +11,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"muzi/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SpotifyTokenURL = "https://accounts.spotify.com/api/token"
|
const SpotifyTokenURL = "https://accounts.spotify.com/api/token"
|
||||||
@@ -44,6 +47,7 @@ type SpotifyCurrentlyPlaying struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SpotifyTrack struct {
|
type SpotifyTrack struct {
|
||||||
|
Id string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
DurationMs int `json:"duration_ms"`
|
DurationMs int `json:"duration_ms"`
|
||||||
Artists []SpotifyArtist `json:"artists"`
|
Artists []SpotifyArtist `json:"artists"`
|
||||||
@@ -64,9 +68,8 @@ type SpotifyRecentPlays struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SpotifyPlayItem struct {
|
type SpotifyPlayItem struct {
|
||||||
Track SpotifyTrack `json:"track"`
|
Track SpotifyTrack `json:"track"`
|
||||||
PlayedAt string `json:"played_at"`
|
PlayedAt string `json:"played_at"`
|
||||||
PlayedAtMs int64 `json:"played_at_ms"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SpotifyCursors struct {
|
type SpotifyCursors struct {
|
||||||
@@ -317,6 +320,8 @@ func checkCurrentlyPlaying(userId int, accessToken string) error {
|
|||||||
artistName = playing.Item.Artists[0].Name
|
artistName = playing.Item.Artists[0].Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkAndScrobbleHalfway(userId, &playing.Item, playing.ProgressMs)
|
||||||
|
|
||||||
UpdateNowPlaying(NowPlaying{
|
UpdateNowPlaying(NowPlaying{
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
SongName: playing.Item.Name,
|
SongName: playing.Item.Name,
|
||||||
@@ -363,9 +368,15 @@ func checkRecentPlays(userId int, accessToken string) error {
|
|||||||
artistName = item.Track.Artists[0].Name
|
artistName = item.Track.Artists[0].Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ts, err := time.Parse(time.RFC3339, item.PlayedAt)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, " -> failed to parse timestamp %s: %v\n", item.PlayedAt, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
scrobbles = append(scrobbles, Scrobble{
|
scrobbles = append(scrobbles, Scrobble{
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
Timestamp: time.Unix(item.PlayedAtMs/1000, 0).UTC(),
|
Timestamp: ts,
|
||||||
SongName: item.Track.Name,
|
SongName: item.Track.Name,
|
||||||
Artist: artistName,
|
Artist: artistName,
|
||||||
Album: item.Track.Album.Name,
|
Album: item.Track.Album.Name,
|
||||||
@@ -409,3 +420,91 @@ func GetSpotifyAuthURL(userId int, baseURL string) (string, error) {
|
|||||||
return fmt.Sprintf("%s?client_id=%s&response_type=code&redirect_uri=%s&scope=%s&state=%d",
|
return fmt.Sprintf("%s?client_id=%s&response_type=code&redirect_uri=%s&scope=%s&state=%d",
|
||||||
SpotifyAuthURL, url.QueryEscape(clientId), url.QueryEscape(redirectURI), url.QueryEscape(scope), userId), nil
|
SpotifyAuthURL, url.QueryEscape(clientId), url.QueryEscape(redirectURI), url.QueryEscape(scope), userId), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LastTrack struct {
|
||||||
|
UserId int
|
||||||
|
TrackId string
|
||||||
|
SongName string
|
||||||
|
Artist string
|
||||||
|
AlbumName string
|
||||||
|
DurationMs int
|
||||||
|
ProgressMs int
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLastTrack(userId int) (*LastTrack, error) {
|
||||||
|
var track LastTrack
|
||||||
|
err := db.Pool.QueryRow(context.Background(),
|
||||||
|
`SELECT user_id, track_id, song_name, artist, album_name, duration_ms, progress_ms, updated_at
|
||||||
|
FROM spotify_last_track WHERE user_id = $1`,
|
||||||
|
userId).Scan(&track.UserId, &track.TrackId, &track.SongName, &track.Artist,
|
||||||
|
&track.AlbumName, &track.DurationMs, &track.ProgressMs, &track.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &track, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLastTrack(userId int, trackId, songName, artist, albumName string, durationMs, progressMs int) error {
|
||||||
|
_, err := db.Pool.Exec(context.Background(),
|
||||||
|
`INSERT INTO spotify_last_track (user_id, track_id, song_name, artist, album_name, duration_ms, progress_ms, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
|
||||||
|
ON CONFLICT (user_id) DO UPDATE SET
|
||||||
|
track_id = $2, song_name = $3, artist = $4, album_name = $5, duration_ms = $6, progress_ms = $7, updated_at = NOW()`,
|
||||||
|
userId, trackId, songName, artist, albumName, durationMs, progressMs)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error saving last track: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAndScrobbleHalfway(userId int, currentTrack *SpotifyTrack, progressMs int) {
|
||||||
|
if currentTrack.Id == "" || currentTrack.DurationMs == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTrack, err := GetLastTrack(userId)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "no rows in result set" {
|
||||||
|
SetLastTrack(userId, currentTrack.Id, currentTrack.Name,
|
||||||
|
getArtistName(currentTrack.Artists), currentTrack.Album.Name, currentTrack.DurationMs, progressMs)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastTrack.TrackId != currentTrack.Id {
|
||||||
|
if lastTrack.DurationMs > 0 {
|
||||||
|
percentagePlayed := float64(lastTrack.ProgressMs) / float64(lastTrack.DurationMs)
|
||||||
|
if percentagePlayed >= 0.5 || lastTrack.ProgressMs >= 240000 {
|
||||||
|
msPlayed := lastTrack.ProgressMs
|
||||||
|
if msPlayed > lastTrack.DurationMs {
|
||||||
|
msPlayed = lastTrack.DurationMs
|
||||||
|
}
|
||||||
|
scrobble := Scrobble{
|
||||||
|
UserId: userId,
|
||||||
|
Timestamp: lastTrack.UpdatedAt,
|
||||||
|
SongName: lastTrack.SongName,
|
||||||
|
Artist: lastTrack.Artist,
|
||||||
|
Album: lastTrack.AlbumName,
|
||||||
|
MsPlayed: msPlayed,
|
||||||
|
Platform: "spotify",
|
||||||
|
}
|
||||||
|
SaveScrobble(scrobble)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetLastTrack(userId, currentTrack.Id, currentTrack.Name,
|
||||||
|
getArtistName(currentTrack.Artists), currentTrack.Album.Name, currentTrack.DurationMs, progressMs)
|
||||||
|
} else {
|
||||||
|
SetLastTrack(userId, currentTrack.Id, currentTrack.Name,
|
||||||
|
getArtistName(currentTrack.Artists), currentTrack.Album.Name, currentTrack.DurationMs, progressMs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getArtistName(artists []SpotifyArtist) string {
|
||||||
|
if len(artists) > 0 {
|
||||||
|
return artists[0].Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user