diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 71fd887e..15ddda75 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: - bundle variables: - VERSION: 0.0.26 + VERSION: 0.0.27 build-go: image: golang:1.16.2 diff --git a/docs/changelog.md b/docs/changelog.md index 9d5b2ab5..161ab879 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,8 +1,11 @@ +# 0.0.27 +- Navidrome works! +- Tidy up request/response structure in backend +- Tidy Settings page + # 0.0.26 - Make email required - Add basic navidrome/subsonic connection -- Tidy up request/response structure in backend -- Tidy Settings page # 0.0.25 - Images now pull from spotify if setup! diff --git a/internal/goscrobble/ingress_navidrome.go b/internal/goscrobble/ingress_navidrome.go index 927f4135..26583c8d 100644 --- a/internal/goscrobble/ingress_navidrome.go +++ b/internal/goscrobble/ingress_navidrome.go @@ -1,10 +1,14 @@ package goscrobble import ( + "database/sql" "encoding/json" "errors" "fmt" + "log" + "net" "net/http" + "time" ) type NavidromeResponse struct { @@ -12,10 +16,41 @@ type NavidromeResponse struct { Status string `json:"status"` Version string `json:"version"` Type string `json:"type"` - Serverversion string `json:"serverVersion"` + ServerVersion string `json:"serverVersion"` + NowPlaying struct { + Entry []NavidromeNowPlaying `json:"entry"` + } `json:"nowPlaying"` } `json:"subsonic-response"` } +type NavidromeNowPlaying struct { + ID string `json:"id"` + Parent string `json:"parent"` + IsDir bool `json:"isDir"` + Title string `json:"title"` + Album string `json:"album"` + Artist string `json:"artist"` + Track int `json:"track"` + Year int `json:"year"` + Genre string `json:"genre"` + CoverArt string `json:"coverArt"` + Size int `json:"size"` + ContentType string `json:"contentType"` + Suffix string `json:"suffix"` + Duration int `json:"duration"` + BitRate int `json:"bitRate"` + Path string `json:"path"` + DiscNumber int `json:"discNumber"` + Created time.Time `json:"created"` + AlbumID string `json:"albumId"` + ArtistID string `json:"artistId"` + Type string `json:"type"` + IsVideo bool `json:"isVideo"` + Username string `json:"username"` + PlayerID int `json:"playerId"` + PlayerName string `json:"playerName"` +} + // updateSpotifyData - Pull data for all users func updateNavidromeData() { // Get all active users with a spotify token @@ -26,20 +61,63 @@ func updateNavidromeData() { } for _, user := range users { - user.updateNavidromePlaydata() + tx, err := db.Begin() + if err != nil { + fmt.Println(err) + continue + } + err = user.updateNavidromePlaydata(tx) + if err != nil { + tx.Rollback() + fmt.Println(err) + continue + } + tx.Commit() } } -func (user *User) updateNavidromePlaydata() { - _, err := user.getNavidromeTokens() +func (user *User) updateNavidromePlaydata(tx *sql.Tx) error { + tokens, err := user.getNavidromeTokens() if err != nil { fmt.Printf("No Navidrome token for user: %+v %+v", user.Username, err) - return + return errors.New("Failed to fetch Navidrome Tokens") } + + response, err := getNavidromeNowPlaying(&tokens) + fmt.Println(response) + if err != nil { + return errors.New(fmt.Sprintf("Failed to fetch Navidrome Tokens %+v", err)) + } + + ip := net.ParseIP("0.0.0.0") + for _, v := range response.Response.NowPlaying.Entry { + err = ParseNavidromeInput(user.UUID, v, ip, tx) + } + + if err != nil { + return err + } + + return nil +} + +func getNavidromeNowPlaying(token *OauthToken) (NavidromeResponse, error) { + response := NavidromeResponse{} + resp, err := http.Get(token.URL + "/rest/getNowPlaying?u=" + token.Username + "&t=" + token.AccessToken + "&s=" + token.RefreshToken + "&c=GoScrobble&v=1.16.1&f=json") + if err != nil { + return response, err + } + + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(&response) + if err != nil { + return response, err + } + + return response, nil } func validateNavidromeConnection(url string, username string, hash string, salt string) error { - fmt.Printf("url:%s, username:%s, hash:%s, salt:%s", url, username, hash, salt) resp, err := http.Get(url + "/rest/ping.view?u=" + username + "&t=" + hash + "&s=" + salt + "&c=GoScrobble&v=1.16.1&f=json") if err != nil { return err @@ -58,3 +136,51 @@ func validateNavidromeConnection(url string, username string, hash string, salt return errors.New("Failed to validate") } + +// ParseNavidromeInput - Transform API data +func ParseNavidromeInput(userUUID string, data NavidromeNowPlaying, ip net.IP, tx *sql.Tx) error { + // Cache key + json := fmt.Sprintf("%s:%s:%s", data.ID, data.Parent, userUUID) + redisKey := getMd5(json) + if getRedisKeyExists(redisKey) { + return nil + } + + artists := make([]string, 0) + + // Insert track artists + artist, err := insertArtist(data.Artist, "", "", "", tx) + if err != nil { + log.Printf("%+v", err) + return errors.New("Failed to map artist: " + artist.Name) + } + artists = append(artists, artist.UUID) + + // Insert album if not exist + album, err := insertAlbum(data.Album, "", "", artists, "", tx) + if err != nil { + log.Printf("%+v", err) + return errors.New("Failed to map album") + } + + // Insert track if not exist + track, err := insertTrack(data.Title, data.Duration, "", "", album.UUID, artists, tx) + if err != nil { + log.Printf("%+v", err) + return errors.New("Failed to map track") + } + + // Insert scrobble if not exist + timeNow := time.Now() + err = insertScrobble(userUUID, track.UUID, "navidrome", timeNow, ip, tx) + if err != nil { + log.Printf("%+v", err) + return errors.New("Failed to map track") + } + + // Todo: Find a better way to check dupes + ttl := time.Duration(data.Duration*2) * time.Second + setRedisValTtl(redisKey, "1", ttl) + + return nil +} diff --git a/internal/goscrobble/server.go b/internal/goscrobble/server.go index 29b7276d..1218ab2c 100644 --- a/internal/goscrobble/server.go +++ b/internal/goscrobble/server.go @@ -733,7 +733,7 @@ func getServerInfo(w http.ResponseWriter, r *http.Request) { } info := ServerInfo{ - Version: "0.0.26", + Version: "0.0.27", RegistrationEnabled: cachedRegistrationEnabled, } diff --git a/internal/goscrobble/timers.go b/internal/goscrobble/timers.go index b2425275..070e2bcb 100644 --- a/internal/goscrobble/timers.go +++ b/internal/goscrobble/timers.go @@ -8,7 +8,6 @@ import ( var endTicker chan bool func StartBackgroundWorkers() { - endTicker := make(chan bool) hourTicker := time.NewTicker(time.Duration(1) * time.Hour) diff --git a/web/src/Components/ScrobbleTable.js b/web/src/Components/ScrobbleTable.js index f6857b6a..2937314f 100644 --- a/web/src/Components/ScrobbleTable.js +++ b/web/src/Components/ScrobbleTable.js @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'; const ScrobbleTable = (props) => { return ( -