Skip to content

Commit

Permalink
feat: get playlist data
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Gleich <[email protected]>
  • Loading branch information
gleich committed Nov 23, 2024
1 parent 426d05f commit 365fa35
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 51 deletions.
38 changes: 23 additions & 15 deletions cmd/lcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ import (
"net/http"
"time"

"github.com/gleich/lcp-v2/internal/apis/applemusic"
"github.com/gleich/lcp-v2/internal/apis/github"
"github.com/gleich/lcp-v2/internal/apis/steam"
"github.com/gleich/lcp-v2/internal/apis/strava"
"github.com/gleich/lcp-v2/internal/secrets"
"github.com/gleich/lumber/v3"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
Expand All @@ -14,21 +21,22 @@ func main() {

secrets.Load()

// r := chi.NewRouter()
// r.Use(middleware.Recoverer)
// r.Use(middleware.RedirectSlashes)
// r.HandleFunc("/", rootRedirect)
// r.HandleFunc("/metrics", promhttp.Handler().ServeHTTP)

// github.Setup(r)
// strava.Setup(r)
// steam.Setup(r)

// lumber.Info("starting server")
// err := http.ListenAndServe(":8000", r)
// if err != nil {
// lumber.Fatal(err, "failed to start router")
// }
r := chi.NewRouter()
r.Use(middleware.Recoverer)
r.Use(middleware.RedirectSlashes)
r.HandleFunc("/", rootRedirect)
r.HandleFunc("/metrics", promhttp.Handler().ServeHTTP)

github.Setup(r)
strava.Setup(r)
steam.Setup(r)
applemusic.Setup(r)

lumber.Info("starting server")
err := http.ListenAndServe(":8000", r)
if err != nil {
lumber.Fatal(err, "failed to start router")
}
}

func setupLogger() {
Expand Down
11 changes: 10 additions & 1 deletion internal/apis/applemusic/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package applemusic

import (
"encoding/json"
"fmt"
"io"
"net/http"

Expand All @@ -11,7 +12,7 @@ import (

func sendAPIRequest[T any](endpoint string) (T, error) {
var zeroValue T // to be used as "nil" when returning errors
req, err := http.NewRequest("GET", endpoint, nil)
req, err := http.NewRequest("GET", "https://api.music.apple.com/"+endpoint, nil)
if err != nil {
lumber.Error(err, "creating request failed")
return zeroValue, err
Expand All @@ -31,6 +32,14 @@ func sendAPIRequest[T any](endpoint string) (T, error) {
lumber.Error(err, "reading response body failed")
return zeroValue, err
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf(
"status code of %d returned from apple music API. Code of 200 expected",
resp.StatusCode,
)
lumber.Error(err)
return zeroValue, err
}

var data T
err = json.Unmarshal(body, &data)
Expand Down
47 changes: 46 additions & 1 deletion internal/apis/applemusic/applemusic.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,52 @@
package applemusic

import "github.com/go-chi/chi/v5"
import (
"time"

"github.com/gleich/lcp-v2/internal/cache"
"github.com/gleich/lumber/v3"
"github.com/go-chi/chi/v5"
)

type cacheData struct {
RecentlyPlayed []song `json:"recently_played"`
Playlists map[string]playlist `json:"playlists"`
}

func cacheUpdate() (cacheData, error) {
recentlyPlayed, err := fetchRecentlyPlayed()
if err != nil {
return cacheData{}, err
}

playlistsIDs := []string{
"p.LV0PXNoCl0EpDLW", // DIVORCED DAD
"p.qQXLxPLtA75zg8e", // HIGHSCHOOL 1989
"p.LV0PX3EIl0EpDLW", // jazz
}
playlists := map[string]playlist{}
for _, id := range playlistsIDs {
playlistData, err := fetchPlaylist(id)
if err != nil {
return cacheData{}, err
}
playlists[id] = playlistData
}

return cacheData{
RecentlyPlayed: recentlyPlayed,
Playlists: playlists,
}, nil
}

func Setup(router *chi.Mux) {
data, err := cacheUpdate()
if err != nil {
lumber.Fatal(err, "initial fetch of cache data failed")
}

applemusicCache := cache.NewCache("applemusic", data)
router.Get("/applemusic/cache", applemusicCache.ServeHTTP())
go applemusicCache.StartPeriodicUpdate(cacheUpdate, 1*time.Minute)
lumber.Done("setup apple music cache")
}
62 changes: 62 additions & 0 deletions internal/apis/applemusic/playlists.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package applemusic

import (
"path"
"time"

"github.com/gleich/lumber/v3"
)

type playlist struct {
Name string `json:"name"`
Tracks []song `json:"tracks"`
LastModified time.Time `json:"last_modified"`
}

type playlistTracksResponse struct {
Next string `json:"next"`
Data []songResponse `json:"data"`
}

type playlistResponse struct {
Data []struct {
Attributes struct {
LastModifiedDate time.Time `json:"lastModifiedDate"`
Name string `json:"name"`
} `json:"attributes"`
} `json:"data"`
}

func fetchPlaylist(id string) (playlist, error) {
playlistData, err := sendAPIRequest[playlistResponse](path.Join("v1/me/library/playlists/", id))
if err != nil {
lumber.Error(err, "failed to fetch playlist for", id)
return playlist{}, err
}

var totalResponseData []songResponse
trackData, err := sendAPIRequest[playlistTracksResponse](
path.Join("v1/me/library/playlists/", id, "tracks"),
)
if err != nil {
lumber.Error(err, "failed to get tracks for playlist with id of", id)
}
totalResponseData = append(totalResponseData, trackData.Data...)
for trackData.Next != "" {
trackData, err = sendAPIRequest[playlistTracksResponse](trackData.Next)
if err != nil {
lumber.Error(err, "failed to paginate through tracks for playlist with id of", id)
}
}

var tracks []song
for _, t := range totalResponseData {
tracks = append(tracks, songFromSongResponse(t))
}

return playlist{
Name: playlistData.Data[0].Attributes.Name,
LastModified: playlistData.Data[0].Attributes.LastModifiedDate,
Tracks: tracks,
}, nil
}
36 changes: 6 additions & 30 deletions internal/apis/applemusic/recent.go
Original file line number Diff line number Diff line change
@@ -1,44 +1,20 @@
package applemusic

import (
"encoding/json"
"strconv"
"strings"

"github.com/gleich/lumber/v3"
)

type recentlyPlayedResponse struct {
Next string `json:"next"`
Data songListData `json:"data"`
Data []songResponse `json:"data"`
}

func FetchRecentlyPlayed() {
func fetchRecentlyPlayed() ([]song, error) {
response, err := sendAPIRequest[recentlyPlayedResponse](
"https://api.music.apple.com/v1/me/recent/played/tracks",
"v1/me/recent/played/tracks",
)
if err != nil {
lumber.Fatal(err, "failed to send request for recently played songs")
return []song{}, err
}

var songs []song
for _, s := range response.Data {
songs = append(songs, song{
Track: s.Attributes.Name,
Artist: s.Attributes.ArtistName,
Album: s.Attributes.AlbumName,
Genres: s.Attributes.GenreNames,
ReleaseDate: s.Attributes.ReleaseDate,
DurationInMillis: s.Attributes.DurationInMillis,
AlbumArtURL: strings.ReplaceAll(strings.ReplaceAll(
s.Attributes.Artwork.URL,
"{w}",
strconv.Itoa(s.Attributes.Artwork.Width),
), "{h}", strconv.Itoa(s.Attributes.Artwork.Height)),
URL: s.Attributes.URL,
})
songs = append(songs, songFromSongResponse(s))
}

encodedData, _ := json.Marshal(songs)
lumber.Debug(string(encodedData))
return songs, nil
}
30 changes: 26 additions & 4 deletions internal/apis/applemusic/song.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package applemusic

import (
"strconv"
"strings"
)

type song struct {
Track string `json:"track"`
Artist string `json:"artist"`
Album string `json:"album"`
Genres []string `json:"genres"`
ReleaseDate string `json:"releaseDate"`
DurationInMillis int `json:"durationInMillis"`
AlbumArtURL string `json:"albumArtURL"`
ReleaseDate string `json:"release_date"`
DurationInMillis int `json:"duration_in_millis"`
AlbumArtURL string `json:"album_art_url"`
URL string `json:"url"`
}

type songListData []struct {
type songResponse struct {
ID string `json:"id"`
Type string `json:"type"`
Href string `json:"href"`
Expand All @@ -31,3 +36,20 @@ type songListData []struct {
ArtistName string `json:"artistName"`
} `json:"attributes"`
}

func songFromSongResponse(s songResponse) song {
return song{
Track: s.Attributes.Name,
Artist: s.Attributes.ArtistName,
Album: s.Attributes.AlbumName,
Genres: s.Attributes.GenreNames,
ReleaseDate: s.Attributes.ReleaseDate,
DurationInMillis: s.Attributes.DurationInMillis,
AlbumArtURL: strings.ReplaceAll(strings.ReplaceAll(
s.Attributes.Artwork.URL,
"{w}",
strconv.Itoa(s.Attributes.Artwork.Width),
), "{h}", strconv.Itoa(s.Attributes.Artwork.Height)),
URL: s.Attributes.URL,
}
}

0 comments on commit 365fa35

Please sign in to comment.