package main import ( "encoding/json" "io/ioutil" "log" "net/http" "strconv" "time" ) type Image struct { Size string `json:"size"` Href string `json:"#text"` } type AlbumInfo struct { Images []Image `json:"image"` Mbid string Name string `json:"title"` Url string Position `json:"@attr"` Artist string } type Date struct { Date string `json:"#text"` Uts string } func (d *Date) ToTime() time.Time { ts, _ := strconv.Atoi(d.Uts) return time.Unix(int64(ts), 0) } type PageAttrs struct { TotalPages string Total string Page string User string } type ArtistInfo struct { Name string Mbid string Url string } type Position struct { TrackNumber string `json:"position"` } type TrackInfo struct { Name string Mbid string Duration string Artist ArtistInfo Album AlbumInfo PlayCount string Listeners string } type LovedTrack struct { Name string Mbid string Url string Date Date Artist ArtistInfo Images []Image } type Playing struct { NowPlaying string } type RecentTrack struct { Name string Mbid string Url string Artist struct { Name string `json:"#text"` Mbid string } Album struct { Name string `json:"#text"` Mbid string } Images []Image `json:"image"` Date Date Playing `json:"@attr"` } func (app *App) LfmQuery(payload map[string]string) []byte { r, _ := http.NewRequest("GET", "http://ws.audioscrobbler.com/2.0/", nil) values := r.URL.Query() values.Add("api_key", app.Config.Lfm.ApiKey) values.Add("format", "json") for key, value := range payload { values.Add(key, value) } r.URL.RawQuery = values.Encode() resp, _ := http.DefaultClient.Do(r) body, _ := ioutil.ReadAll(resp.Body) resp.Body.Close() return body } func GetImage(images []Image, size string) string { for _, image := range images { if image.Size == size { return image.Href } } return "https://lastfm-img2.akamaized.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.png" } func (app *App) AlbumInfo(artist, album string) AlbumInfo { r := app.LfmQuery(map[string]string{ "method": "album.getInfo", "artist": artist, "album": album, }) var dst map[string]AlbumInfo json.Unmarshal(r, &dst) return dst["album"] } func (app *App) TrackInfo(artist, name string) TrackInfo { r := app.LfmQuery(map[string]string{ "method": "track.getInfo", "artist": artist, "track": name, "autocorrect": "1", }) var dst map[string]TrackInfo json.Unmarshal(r, &dst) return dst["track"] } type RecentTracks struct { dst map[string]struct { Attrs PageAttrs `json:"@attr"` Tracks []RecentTrack `json:"track"` } payload map[string]string *App } func RecentTrackToScrobble(r *RecentTrack, s *Session) Scrobble { scrobble := Scrobble{ Artist: NewCorrectable(r.Artist.Name), Album: NewCorrectable(r.Album.Name), TrackName: NewCorrectable(r.Name), Mbid: r.Mbid, Time: r.Date.ToTime(), UserId: s.UserId, SessionKey: s.Key, Image: GetImage(r.Images, "medium"), } return scrobble } func NewRecentTracks(name string, from, to time.Time, app *App) *RecentTracks { rt := &RecentTracks{App: app} rt.payload = map[string]string{ "method": "user.getRecentTracks", "limit": "200", "user": name, "to": strconv.Itoa(int(to.Unix())), } if from.IsZero() { rt.payload["from"] = "0" } else { rt.payload["from"] = strconv.Itoa(int(from.Unix())) } return rt } func (rt *RecentTracks) Next() []RecentTrack { r := rt.App.LfmQuery(rt.payload) json.Unmarshal(r, &rt.dst) tracks := rt.dst["recenttracks"].Tracks if len(tracks) > 0 { d := tracks[len(tracks)-1].Date if d.Uts != "" && d.Uts != "0" { rt.payload["to"] = d.Uts } } return tracks } func (app *App) ImportRecentTracks(user *User) { s := &Session{ UserId: user.Id, Client: "import", ClientVersion: "", Key: randomToken(16), Protocol: "1.2.1", } app.PutSession(s) i := app.NewImport(user.LfmName) rt := NewRecentTracks(user.LfmName, i.From, i.To, app) scrobbles := make([]Scrobble, 0, 200) for tracks := rt.Next(); len(tracks) > 0; tracks = rt.Next() { scrobbles = scrobbles[:0] for _, t := range tracks { if t.NowPlaying == "true" { continue } scrobble := RecentTrackToScrobble(&t, s) app.GetScrobbleSong(&scrobble) scrobbles = append(scrobbles, scrobble) } if len(scrobbles) == 0 { break } if err := app.PutScrobbles(scrobbles); err != nil { log.Println(err) } i.LastFetch = scrobbles[len(scrobbles)-1].Time i.Count += len(scrobbles) if err := app.SaveImport(i); err != nil { log.Println(err) } } i.Done = true if err := app.SaveImport(i); err != nil { log.Println(err) } } type LovedTracks struct { dst map[string]struct { Attrs PageAttrs `json:"@attr"` Tracks []LovedTrack `json:"track"` } payload map[string]string page int done bool *App } func NewLovedTracks(name string, app *App) *LovedTracks { lt := &LovedTracks{App: app, page: 1} lt.payload = map[string]string{ "method": "user.getLovedTracks", "limit": "200", "user": name, } return lt } func (lt *LovedTracks) Next() []LovedTrack { if lt.done { return nil } lt.payload["page"] = strconv.Itoa(lt.page) r := lt.App.LfmQuery(lt.payload) json.Unmarshal(r, <.dst) root := lt.dst["lovedtracks"] totalPages, _ := strconv.Atoi(root.Attrs.TotalPages) if totalPages == lt.page { lt.done = true } lt.page++ return root.Tracks } func (app *App) ImportLovedTracks(user *User) { s := &Session{ UserId: user.Id, Client: "import", ClientVersion: "", Key: randomToken(16), Protocol: "1.2.1", } app.PutSession(s) ll, _ := app.LoveImportStats(user.LfmName) lt := NewLovedTracks(user.LfmName, app) ltracks := make([]LovedTrack, 0, 200) li := LoveImport{Count: 0, LfmName: user.LfmName} for tracks := lt.Next(); len(tracks) > 0; tracks = lt.Next() { ltracks = ltracks[:0] for _, t := range tracks { if t.Date.ToTime().Before(ll.Time) { break } ltracks = append(ltracks, t) } if err := app.InsertLovedTracks(ltracks, s); err != nil { log.Println(err) return } li.Count += len(ltracks) } if err := app.InsertLoveImport(&li); err != nil { log.Println(err) } } func (app *App) GetSong(s *Song) { if s.Album != "" || s.Mbid != "" { if app.GetSongId(s) != nil { if s.Image == "" { s.Image = GetImage(app.AlbumInfo(s.Artist, s.Album).Images, "medium") } if err := app.InsertSong(s); err != nil { log.Println(err) } } } else { t := app.TrackInfo(s.Artist, s.Name) s.Album = t.Album.Name s.Mbid = t.Mbid s.Image = GetImage(t.Album.Images, "medium") if app.GetSongId(s) != nil { app.InsertSong(s) } } }