Merge pull request #39 from JacksonTaylorxyz/jacksontaylorxyz/feat/add-support-for-album-view
Add support for album view
This commit is contained in:
commit
21ee3aa54e
139
album.go
Normal file
139
album.go
Normal file
@ -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)
|
||||||
|
}
|
21
lyrics.go
21
lyrics.go
@ -11,12 +11,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type song struct {
|
type song struct {
|
||||||
Artist string
|
Artist string
|
||||||
Title string
|
Title string
|
||||||
Image string
|
Image string
|
||||||
Lyrics string
|
Lyrics string
|
||||||
Credits map[string]string
|
Credits map[string]string
|
||||||
About [2]string
|
About [2]string
|
||||||
|
Album string
|
||||||
|
LinkToAlbum string
|
||||||
}
|
}
|
||||||
|
|
||||||
type songResponse struct {
|
type songResponse struct {
|
||||||
@ -28,6 +30,10 @@ type songResponse struct {
|
|||||||
Description struct {
|
Description struct {
|
||||||
Plain string
|
Plain string
|
||||||
}
|
}
|
||||||
|
Album struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
CustomPerformances []customPerformance `json:"custom_performances"`
|
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")
|
attr, exists := doc.Find("meta[property='twitter:app:url:iphone']").Attr("content")
|
||||||
if exists {
|
if exists {
|
||||||
songID := strings.Replace(attr, "genius://songs/", "", 1)
|
songID := strings.Replace(attr, "genius://songs/", "", 1)
|
||||||
|
|
||||||
u := fmt.Sprintf("https://genius.com/api/songs/%s?text_format=plain", songID)
|
u := fmt.Sprintf("https://genius.com/api/songs/%s?text_format=plain", songID)
|
||||||
|
|
||||||
res, err := sendRequest(u)
|
res, err := sendRequest(u)
|
||||||
@ -77,6 +84,8 @@ func (s *song) parseSongData(doc *goquery.Document) {
|
|||||||
s.About[0] = songData.Description.Plain
|
s.About[0] = songData.Description.Plain
|
||||||
s.About[1] = truncateText(songData.Description.Plain)
|
s.About[1] = truncateText(songData.Description.Plain)
|
||||||
s.Credits = make(map[string]string)
|
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 {
|
for _, perf := range songData.CustomPerformances {
|
||||||
var artists []string
|
var artists []string
|
||||||
|
1
main.go
1
main.go
@ -31,6 +31,7 @@ func main() {
|
|||||||
r.HandleFunc("/{id}-lyrics", lyricsHandler)
|
r.HandleFunc("/{id}-lyrics", lyricsHandler)
|
||||||
r.HandleFunc("/images/{filename}.{ext}", proxyHandler)
|
r.HandleFunc("/images/{filename}.{ext}", proxyHandler)
|
||||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
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) {
|
r.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
render("error", w, map[string]string{
|
render("error", w, map[string]string{
|
||||||
|
@ -101,10 +101,11 @@ a {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
#metadata > img {
|
#album-artwork {
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: 0 1px 1px #ddd;
|
box-shadow: 0 1px 1px #ddd;
|
||||||
|
max-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#container {
|
#container {
|
||||||
@ -130,6 +131,15 @@ a {
|
|||||||
color: #1e1e1e;
|
color: #1e1e1e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#album-tracklist{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-basis: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
#credits p {
|
#credits p {
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
35
views/album.tmpl
Normal file
35
views/album.tmpl
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{.Artist}} - {{.Name}}</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/style.css" />
|
||||||
|
<script type="text/javascript" src="/static/script.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{template "navbar"}}
|
||||||
|
<div id="container">
|
||||||
|
<div id="metadata">
|
||||||
|
<img id="album-artwork" src="{{extractURL .Image}}"/>
|
||||||
|
<h2>{{.Artist}}</h2>
|
||||||
|
<h1>{{.Name}}</h1>
|
||||||
|
</div>
|
||||||
|
<div id="album-tracklist">
|
||||||
|
{{range .Tracks}}
|
||||||
|
<a href="{{.Url}}">
|
||||||
|
<p>{{.Title}}</p>
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div id="info">
|
||||||
|
<div id="about">
|
||||||
|
<h1 id="title">About</h1>
|
||||||
|
<p class="hidden" id="full_about">{{index .About 0}}</p>
|
||||||
|
<p id="summary">{{index .About 1}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "footer"}}
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -11,9 +11,10 @@
|
|||||||
{{template "navbar"}}
|
{{template "navbar"}}
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div id="metadata">
|
<div id="metadata">
|
||||||
<img src="{{extractURL .Image}}"/>
|
<a href="{{.LinkToAlbum}}"><img id="album-artwork" src="{{extractURL .Image}}"/></a>
|
||||||
<h2>{{.Artist}}</h2>
|
<h2>{{.Artist}}</h2>
|
||||||
<h1>{{.Title}}</h1>
|
<h1>{{.Title}}</h1>
|
||||||
|
<a href="{{.LinkToAlbum}}"><h2>{{.Album}}</h2></a>
|
||||||
</div>
|
</div>
|
||||||
<div id="lyrics">{{.Lyrics}}</div>
|
<div id="lyrics">{{.Lyrics}}</div>
|
||||||
<div id="info">
|
<div id="info">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user