2021-04-02 09:24:00 +00:00
|
|
|
package goscrobble
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/zmb3/spotify"
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// updateSpotifyData - Pull data for all users
|
|
|
|
func updateSpotifyData() {
|
|
|
|
// Lets ignore if not configured
|
|
|
|
val, _ := getConfigValue("SPOTIFY_APP_SECRET")
|
|
|
|
if val == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all active users with a spotify token
|
|
|
|
users, err := getAllSpotifyUsers()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to fetch spotify users")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, user := range users {
|
|
|
|
user.updateSpotifyPlaydata()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getSpotifyAuthHandler() spotify.Authenticator {
|
|
|
|
appId, _ := getConfigValue("SPOTIFY_APP_ID")
|
|
|
|
appSecret, _ := getConfigValue("SPOTIFY_APP_SECRET")
|
|
|
|
|
|
|
|
redirectUrl := os.Getenv("GOSCROBBLE_DOMAIN") + "/api/v1/link/spotify"
|
|
|
|
if redirectUrl == "http://localhost:3000/api/v1/link/spotify" {
|
|
|
|
// Handle backend on a different port
|
|
|
|
redirectUrl = "http://localhost:42069/api/v1/link/spotify"
|
|
|
|
}
|
|
|
|
|
|
|
|
auth := spotify.NewAuthenticator(redirectUrl,
|
|
|
|
spotify.ScopeUserReadRecentlyPlayed, spotify.ScopeUserReadCurrentlyPlaying)
|
|
|
|
|
|
|
|
auth.SetAuthInfo(appId, appSecret)
|
|
|
|
|
|
|
|
return auth
|
|
|
|
}
|
|
|
|
|
|
|
|
func connectSpotifyResponse(r *http.Request) error {
|
|
|
|
urlParams := r.URL.Query()
|
|
|
|
userUuid := urlParams["state"][0]
|
|
|
|
|
|
|
|
// TODO: Add validation user exists here
|
|
|
|
|
|
|
|
auth := getSpotifyAuthHandler()
|
|
|
|
token, err := auth.Token(userUuid, r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get displayName
|
|
|
|
client := auth.NewClient(token)
|
|
|
|
client.AutoRetry = true
|
|
|
|
spotifyUser, err := client.CurrentUser()
|
|
|
|
|
|
|
|
// Lets pull in last 30 minutes
|
|
|
|
time := time.Now().UTC().Add(-(time.Duration(30) * time.Minute))
|
|
|
|
err = insertOauthToken(userUuid, "spotify", token.AccessToken, token.RefreshToken, token.Expiry, spotifyUser.DisplayName, time)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (user *User) updateSpotifyPlaydata() {
|
|
|
|
dbToken, err := user.getSpotifyTokens()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("No spotify token for user: %+v %+v", user.Username, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
token := new(oauth2.Token)
|
|
|
|
token.AccessToken = dbToken.AccessToken
|
|
|
|
token.RefreshToken = dbToken.RefreshToken
|
|
|
|
token.Expiry = dbToken.Expiry
|
|
|
|
token.TokenType = "Bearer"
|
|
|
|
|
|
|
|
auth := getSpotifyAuthHandler()
|
|
|
|
client := auth.NewClient(token)
|
|
|
|
client.AutoRetry = true
|
|
|
|
|
|
|
|
// Only fetch tracks since last sync
|
|
|
|
opts := spotify.RecentlyPlayedOptions{
|
|
|
|
AfterEpochMs: dbToken.LastSynced.UnixNano() / int64(time.Millisecond),
|
|
|
|
}
|
|
|
|
|
|
|
|
// We want the next sync timestamp from before we call
|
|
|
|
// so we don't end up with a few seconds gap
|
|
|
|
currTime := time.Now()
|
|
|
|
items, err := client.PlayerRecentlyPlayedOpt(&opts)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
fmt.Printf("Unable to get recently played tracks for user: %+v", user.Username)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range items {
|
|
|
|
if !checkIfSpotifyAlreadyScrobbled(user.UUID, v) {
|
|
|
|
tx, _ := db.Begin()
|
|
|
|
err := ParseSpotifyInput(user.UUID, v, client, tx)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to insert Spotify scrobble: %+v", err)
|
|
|
|
tx.Rollback()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if token has changed.. if so, save it to db
|
|
|
|
currToken, err := client.Token()
|
|
|
|
err = insertOauthToken(user.UUID, "spotify", currToken.AccessToken, currToken.RefreshToken, currToken.Expiry, dbToken.Username, currTime)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to update spotify token in database")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkIfSpotifyAlreadyScrobbled(userUuid string, data spotify.RecentlyPlayedItem) bool {
|
|
|
|
return checkIfScrobbleExists(userUuid, data.PlayedAt, "spotify")
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseSpotifyInput - Transform API data
|
|
|
|
func ParseSpotifyInput(userUUID string, data spotify.RecentlyPlayedItem, client spotify.Client, tx *sql.Tx) error {
|
|
|
|
artists := make([]string, 0)
|
|
|
|
albumartists := make([]string, 0)
|
|
|
|
|
|
|
|
// Insert track artists
|
|
|
|
for _, artist := range data.Track.Artists {
|
2021-04-08 07:50:43 +00:00
|
|
|
fullArtist, err := client.GetArtist(artist.ID)
|
|
|
|
img := ""
|
|
|
|
if len(fullArtist.Images) > 0 {
|
|
|
|
img = fullArtist.Images[0].URL
|
|
|
|
}
|
|
|
|
|
|
|
|
artist, err := insertArtist(artist.Name, "", artist.ID.String(), img, tx)
|
2021-04-02 09:24:00 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("%+v", err)
|
|
|
|
return errors.New("Failed to map artist: " + artist.Name)
|
|
|
|
}
|
2021-04-04 09:54:53 +00:00
|
|
|
artists = append(artists, artist.UUID)
|
2021-04-02 09:24:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get full track data (album / track info)
|
|
|
|
fulltrack, err := client.GetTrack(data.Track.ID)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to get full track info from spotify: %+v", data.Track.Name)
|
|
|
|
return errors.New("Failed to get full track info from spotify: " + data.Track.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert album artists
|
|
|
|
for _, artist := range fulltrack.Album.Artists {
|
2021-04-08 07:50:43 +00:00
|
|
|
fullArtist, err := client.GetArtist(artist.ID)
|
|
|
|
img := ""
|
|
|
|
if len(fullArtist.Images) > 0 {
|
|
|
|
img = fullArtist.Images[0].URL
|
|
|
|
}
|
|
|
|
|
|
|
|
albumartist, err := insertArtist(artist.Name, "", artist.ID.String(), img, tx)
|
2021-04-02 09:24:00 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("%+v", err)
|
|
|
|
return errors.New("Failed to map album: " + artist.Name)
|
|
|
|
}
|
2021-04-04 09:54:53 +00:00
|
|
|
albumartists = append(albumartists, albumartist.UUID)
|
2021-04-02 09:24:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Insert album if not exist
|
2021-04-08 07:50:43 +00:00
|
|
|
albumImage := ""
|
|
|
|
if len(fulltrack.Album.Images) > 0 {
|
|
|
|
albumImage = fulltrack.Album.Images[0].URL
|
|
|
|
}
|
|
|
|
album, err := insertAlbum(fulltrack.Album.Name, "", fulltrack.Album.ID.String(), albumartists, albumImage, tx)
|
2021-04-02 09:24:00 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("%+v", err)
|
|
|
|
return errors.New("Failed to map album")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert track if not exist
|
2021-04-02 13:53:54 +00:00
|
|
|
length := int(fulltrack.Duration / 1000)
|
2021-04-04 09:54:53 +00:00
|
|
|
track, err := insertTrack(fulltrack.Name, length, "", fulltrack.ID.String(), album.UUID, artists, tx)
|
2021-04-02 09:24:00 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("%+v", err)
|
|
|
|
return errors.New("Failed to map track")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert scrobble if not exist
|
|
|
|
ip := net.ParseIP("0.0.0.0")
|
2021-04-04 09:54:53 +00:00
|
|
|
err = insertScrobble(userUUID, track.UUID, "spotify", data.PlayedAt, ip, tx)
|
2021-04-02 09:24:00 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("%+v", err)
|
|
|
|
return errors.New("Failed to map track")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2021-04-08 07:50:43 +00:00
|
|
|
|
|
|
|
func (track *Track) updateImageFromSpotify() error {
|
|
|
|
return nil
|
|
|
|
}
|