···316 return nil
317 }
318319- uts, err := strconv.ParseInt(lastNonNowPlaying.Date.UTS, 10, 64)
320- if err != nil {
321- log.Printf("error parsing timestamp '%s' for track %s - %s: %v",
322- lastNonNowPlaying.Date.UTS, lastNonNowPlaying.Artist.Text, lastNonNowPlaying.Name, err)
323- }
324- latestTrackTime := time.Unix(uts, 0)
325326 // print both
327 fmt.Printf("latestTrackTime: %s\n", latestTrackTime)
328 fmt.Printf("lastKnownTimestamp: %s\n", lastKnownTimestamp)
329330- if lastKnownTimestamp != nil && lastKnownTimestamp.Equal(latestTrackTime) {
331 log.Printf("no new tracks to process for user %s.", username)
332 return nil
333 }
334335 for _, track := range tracks {
336- if track.Date == nil || track.Date.UTS == "" {
337 log.Printf("skipping track without timestamp for %s: %s - %s", username, track.Artist.Text, track.Name)
338 continue
339 }
340341- uts, err := strconv.ParseInt(track.Date.UTS, 10, 64)
342- if err != nil {
343- log.Printf("error parsing timestamp '%s' for track %s - %s: %v", track.Date.UTS, track.Artist.Text, track.Name, err)
344- continue
345- }
346- trackTime := time.Unix(uts, 0)
347-348 // before or at last known
349 if lastKnownTimestamp != nil && (trackTime.Before(*lastKnownTimestamp) || trackTime.Equal(*lastKnownTimestamp)) {
350 if processedCount == 0 {
···316 return nil
317 }
318319+ latestTrackTime := lastNonNowPlaying.Date
00000320321 // print both
322 fmt.Printf("latestTrackTime: %s\n", latestTrackTime)
323 fmt.Printf("lastKnownTimestamp: %s\n", lastKnownTimestamp)
324325+ if lastKnownTimestamp != nil && lastKnownTimestamp.Equal(latestTrackTime.Time) {
326 log.Printf("no new tracks to process for user %s.", username)
327 return nil
328 }
329330 for _, track := range tracks {
331+ if track.Date == nil {
332 log.Printf("skipping track without timestamp for %s: %s - %s", username, track.Artist.Text, track.Name)
333 continue
334 }
335336+ trackTime := track.Date.Time
000000337 // before or at last known
338 if lastKnownTimestamp != nil && (trackTime.Before(*lastKnownTimestamp) || trackTime.Equal(*lastKnownTimestamp)) {
339 if processedCount == 0 {
+36-2
service/lastfm/model.go
···1package lastfm
20000003// Structs to represent the Last.fm API response for user.getrecenttracks
4type RecentTracksResponse struct {
5 RecentTracks RecentTracks `json:"recenttracks"`
···19 Name string `json:"name"`
20 URL string `json:"url"`
21 Date *TrackDate `json:"date,omitempty"` // Use pointer for optional fields
22- Attr *struct { // Custom handling for @attr.nowplaying
23 NowPlaying string `json:"nowplaying"` // Field name corrected to match struct tag
24 } `json:"@attr,omitempty"` // This captures the @attr object within the track
25}
···39 Text string `json:"#text"` // Album name
40}
4142-type TrackDate struct {
000043 UTS string `json:"uts"` // Unix timestamp string
44 Text string `json:"#text"` // Human-readable date string
00000000000000000000000045}
4647type TrackXMLAttr struct {
···1package lastfm
23+import (
4+ "encoding/json"
5+ "strconv"
6+ "time"
7+)
8+9// Structs to represent the Last.fm API response for user.getrecenttracks
10type RecentTracksResponse struct {
11 RecentTracks RecentTracks `json:"recenttracks"`
···25 Name string `json:"name"`
26 URL string `json:"url"`
27 Date *TrackDate `json:"date,omitempty"` // Use pointer for optional fields
28+ Attr *struct { // Custom handling for @attr.nowplaying
29 NowPlaying string `json:"nowplaying"` // Field name corrected to match struct tag
30 } `json:"@attr,omitempty"` // This captures the @attr object within the track
31}
···45 Text string `json:"#text"` // Album name
46}
4748+// ApiTrackDate This is the real structure returned from lastFM.
49+// Represents a date associated with a track, including both a Unix timestamp and a human-readable string.
50+// UTS is a Unix timestamp stored as a string.
51+// Text contains the human-readable date string.
52+type ApiTrackDate struct {
53 UTS string `json:"uts"` // Unix timestamp string
54 Text string `json:"#text"` // Human-readable date string
55+}
56+57+// TrackDate This is the struct we use to represent a date associated with a track.
58+// It is a wrapper around time.Time that implements json.Unmarshaler.
59+type TrackDate struct {
60+ time.Time
61+}
62+63+// UnmarshalJSON Implements json.Unmarshaler.
64+// Parses the UTS field from the API response and converts it to a time.Time.
65+// The time.Time is stored in the Time field.
66+// The Text field is ignored since it can be parsed from the Time field if needed.
67+func (t *TrackDate) UnmarshalJSON(b []byte) (err error) {
68+ var apiTrackDate ApiTrackDate
69+ if err := json.Unmarshal(b, &apiTrackDate); err != nil {
70+ return err
71+ }
72+ uts, err := strconv.ParseInt(apiTrackDate.UTS, 10, 64)
73+ if err != nil {
74+ return err
75+ }
76+ date := time.Unix(uts, 0).UTC()
77+ t.Time = date
78+ return
79}
8081type TrackXMLAttr struct {