From 273176a57c04305e20039ae47a866897cceffb0a Mon Sep 17 00:00:00 2001 From: Jackson Taylor Date: Fri, 30 Jun 2023 12:27:31 -0400 Subject: [PATCH 1/2] feat: add album handler Serves a page including the album name, artist, track list, and credits. Each of the tracks have links to the lyrics page, provided there is one available. --- album.go | 139 +++++++++++++++++++++++++++++++++++++++++++++++ main.go | 1 + static/style.css | 10 ++++ views/album.tmpl | 35 ++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 album.go create mode 100644 views/album.tmpl diff --git a/album.go b/album.go new file mode 100644 index 0000000..e21d8a2 --- /dev/null +++ b/album.go @@ -0,0 +1,139 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/PuerkitoBio/goquery" + "github.com/gorilla/mux" +) + +type album struct { + Artist string + Name string + Image string + About [2]string + + Tracks []Track +} + +type Track struct { + Title string + Url string +} + +type albumMetadata struct { + Album struct { + Id int `json:"id"` + Image string `json:"cover_art_thumbnail_url"` + Name string `json:"name"` + Description string `json:"description_preview"` + Artist `json:"artist"` + } + AlbumAppearances []AlbumAppearances `json:"album_appearances"` +} + +type AlbumAppearances struct { + Id int `json:"id"` + TrackNumber int `json:"track_number"` + Song struct { + Title string `json:"title"` + Url string `json:"url"` + } +} + +type Artist struct { + Name string `json:"name"` +} + +func (a *album) parseAlbumData(doc *goquery.Document) { + pageMetadata, exists := doc.Find("meta[itemprop='page_data']").Attr("content") + if !exists { + return + } + + var albumMetadataFromPage albumMetadata + json.Unmarshal([]byte(pageMetadata), &albumMetadataFromPage) + + albumData := albumMetadataFromPage.Album + a.Artist = albumData.Artist.Name + a.Name = albumData.Name + a.Image = albumData.Image + a.About[0] = albumData.Description + a.About[1] = truncateText(albumData.Description) + + for _, track := range albumMetadataFromPage.AlbumAppearances { + url := strings.Replace(track.Song.Url, "https://genius.com", "", -1) + a.Tracks = append(a.Tracks, Track{Title: track.Song.Title, Url: url}) + } +} + +func (a *album) parse(doc *goquery.Document) { + a.parseAlbumData(doc) +} + +func albumHandler(w http.ResponseWriter, r *http.Request) { + artist := mux.Vars(r)["artist"] + albumName := mux.Vars(r)["albumName"] + + id := fmt.Sprintf("%s/%s", artist, albumName) + + if data, err := getCache(id); err == nil { + render("album", w, data) + return + } + + url := fmt.Sprintf("https://genius.com/albums/%s/%s", artist, albumName) + + resp, err := sendRequest(url) + if err != nil { + logger.Errorln(err) + w.WriteHeader(http.StatusInternalServerError) + render("error", w, map[string]string{ + "Status": "500", + "Error": "cannot reach genius servers", + }) + return + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + w.WriteHeader(http.StatusNotFound) + render("error", w, map[string]string{ + "Status": "404", + "Error": "page not found", + }) + return + } + + doc, err := goquery.NewDocumentFromReader(resp.Body) + if err != nil { + logger.Errorln(err) + w.WriteHeader(http.StatusInternalServerError) + render("error", w, map[string]string{ + "Status": "500", + "Error": "something went wrong", + }) + return + } + + cf := doc.Find(".cloudflare_content").Length() + if cf > 0 { + logger.Errorln("cloudflare got in the way") + render("error", w, map[string]string{ + "Status": "500", + "Error": "damn cloudflare, issue #21 on GitHub", + }) + return + } + + var a album + a.parse(doc) + + render("album", w, a) + + setCache(id, a) +} diff --git a/main.go b/main.go index 0161943..9b8c582 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ func main() { r.HandleFunc("/{id}-lyrics", lyricsHandler) r.HandleFunc("/images/{filename}.{ext}", proxyHandler) r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) + r.HandleFunc("/albums/{artist}/{albumName}", albumHandler) r.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) render("error", w, map[string]string{ diff --git a/static/style.css b/static/style.css index 2696a9e..e166a83 100644 --- a/static/style.css +++ b/static/style.css @@ -105,6 +105,7 @@ a { width: 20rem; border-radius: 3px; box-shadow: 0 1px 1px #ddd; + max-width: 200px; } #container { @@ -130,6 +131,15 @@ a { color: #1e1e1e; } +#album-tracklist{ + display: flex; + flex-direction: column; + gap: 1rem; + flex-basis: 0; + flex-shrink: 0; + flex-grow: 1; +} + #credits p { font-size: 1.3rem; padding: 0.5rem; diff --git a/views/album.tmpl b/views/album.tmpl new file mode 100644 index 0000000..cb2a2c6 --- /dev/null +++ b/views/album.tmpl @@ -0,0 +1,35 @@ + + + + {{.Artist}} - {{.Name}} + + + + + + + {{template "navbar"}} +
+
+ +

{{.Artist}}

+

{{.Name}}

+
+
+ {{range .Tracks}} + +

{{.Title}}

+
+ {{end}} +
+
+
+

About

+ +

{{index .About 1}}

+
+
+
+ {{template "footer"}} + + From 9c8ed042a5ad6afaa3cfe8aad2f760ad1a36dc73 Mon Sep 17 00:00:00 2001 From: Jackson Taylor Date: Tue, 5 Sep 2023 16:47:21 -0400 Subject: [PATCH 2/2] feat: add link to song's album on lyrics page --- lyrics.go | 21 +++++++++++++++------ static/style.css | 2 +- views/album.tmpl | 2 +- views/lyrics.tmpl | 3 ++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lyrics.go b/lyrics.go index aa943f0..0fdcb8c 100644 --- a/lyrics.go +++ b/lyrics.go @@ -11,12 +11,14 @@ import ( ) type song struct { - Artist string - Title string - Image string - Lyrics string - Credits map[string]string - About [2]string + Artist string + Title string + Image string + Lyrics string + Credits map[string]string + About [2]string + Album string + LinkToAlbum string } type songResponse struct { @@ -28,6 +30,10 @@ type songResponse struct { Description struct { Plain string } + Album struct { + Url string `json:"url"` + Name string `json:"name"` + } CustomPerformances []customPerformance `json:"custom_performances"` } } @@ -54,6 +60,7 @@ func (s *song) parseSongData(doc *goquery.Document) { attr, exists := doc.Find("meta[property='twitter:app:url:iphone']").Attr("content") if exists { songID := strings.Replace(attr, "genius://songs/", "", 1) + u := fmt.Sprintf("https://genius.com/api/songs/%s?text_format=plain", songID) res, err := sendRequest(u) @@ -77,6 +84,8 @@ func (s *song) parseSongData(doc *goquery.Document) { s.About[0] = songData.Description.Plain s.About[1] = truncateText(songData.Description.Plain) s.Credits = make(map[string]string) + s.Album = songData.Album.Name + s.LinkToAlbum = strings.Replace(songData.Album.Url, "https://genius.com", "", -1) for _, perf := range songData.CustomPerformances { var artists []string diff --git a/static/style.css b/static/style.css index e166a83..1ced795 100644 --- a/static/style.css +++ b/static/style.css @@ -101,7 +101,7 @@ a { font-weight: 500; } -#metadata > img { +#album-artwork { width: 20rem; border-radius: 3px; box-shadow: 0 1px 1px #ddd; diff --git a/views/album.tmpl b/views/album.tmpl index cb2a2c6..db35d77 100644 --- a/views/album.tmpl +++ b/views/album.tmpl @@ -11,7 +11,7 @@ {{template "navbar"}}
- +

{{.Artist}}

{{.Name}}

diff --git a/views/lyrics.tmpl b/views/lyrics.tmpl index d665d73..f157298 100644 --- a/views/lyrics.tmpl +++ b/views/lyrics.tmpl @@ -11,9 +11,10 @@ {{template "navbar"}}
- +

{{.Artist}}

{{.Title}}

+

{{.Album}}

{{.Lyrics}}