feat: add artist page
This commit is contained in:
parent
0381b6e4ae
commit
840d23e931
@ -7,10 +7,15 @@ import (
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type Album struct {
|
||||
Artist string
|
||||
type AlbumPreview struct {
|
||||
Name string
|
||||
Image string
|
||||
URL string
|
||||
}
|
||||
|
||||
type Album struct {
|
||||
AlbumPreview
|
||||
Artist ArtistPreview
|
||||
About [2]string
|
||||
|
||||
Tracks []Track
|
||||
@ -28,7 +33,7 @@ type albumMetadata struct {
|
||||
Image string `json:"cover_art_thumbnail_url"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description_preview"`
|
||||
Artist `json:"artist"`
|
||||
artistPreviewMetadata `json:"artist"`
|
||||
}
|
||||
AlbumAppearances []AlbumAppearances `json:"album_appearances"`
|
||||
}
|
||||
@ -42,8 +47,9 @@ type AlbumAppearances struct {
|
||||
}
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
type artistPreviewMetadata struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func (a *Album) parseAlbumData(doc *goquery.Document) error {
|
||||
@ -58,7 +64,10 @@ func (a *Album) parseAlbumData(doc *goquery.Document) error {
|
||||
}
|
||||
|
||||
albumData := albumMetadataFromPage.Album
|
||||
a.Artist = albumData.Artist.Name
|
||||
a.Artist = ArtistPreview{
|
||||
Name: albumData.artistPreviewMetadata.Name,
|
||||
URL: strings.Replace(albumData.artistPreviewMetadata.URL, "https://genius.com", "", -1),
|
||||
}
|
||||
a.Name = albumData.Name
|
||||
a.Image = albumData.Image
|
||||
a.About[0] = albumData.Description
|
||||
|
65
data/artist.go
Normal file
65
data/artist.go
Normal file
@ -0,0 +1,65 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type ArtistPreview struct {
|
||||
Name string
|
||||
URL string
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
Name string
|
||||
Description string
|
||||
Albums []AlbumPreview
|
||||
Image string
|
||||
}
|
||||
|
||||
type artistMetadata struct {
|
||||
Artist struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description_preview"`
|
||||
Image string `json:"image_url"`
|
||||
}
|
||||
Albums []struct {
|
||||
Id int `json:"id"`
|
||||
Image string `json:"cover_art_thumbnail_url"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
} `json:"artist_albums"`
|
||||
}
|
||||
|
||||
func (a *Artist) parseArtistData(doc *goquery.Document) error {
|
||||
pageMetadata, exists := doc.Find("meta[itemprop='page_data']").Attr("content")
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
var artistMetadataFromPage artistMetadata
|
||||
if err := json.Unmarshal([]byte(pageMetadata), &artistMetadataFromPage); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.Name = artistMetadataFromPage.Artist.Name
|
||||
a.Description = artistMetadataFromPage.Artist.Description
|
||||
a.Image = artistMetadataFromPage.Artist.Image
|
||||
|
||||
for _, album := range artistMetadataFromPage.Albums {
|
||||
a.Albums = append(a.Albums, AlbumPreview{
|
||||
Name: album.Name,
|
||||
Image: album.Image,
|
||||
URL: strings.Replace(album.URL, "https://genius.com", "", -1),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artist) Parse(doc *goquery.Document) error {
|
||||
return a.parseArtistData(doc)
|
||||
}
|
@ -16,11 +16,7 @@ type Song struct {
|
||||
Lyrics string
|
||||
Credits map[string]string
|
||||
About [2]string
|
||||
Album struct {
|
||||
URL string
|
||||
Name string
|
||||
Image string
|
||||
}
|
||||
Album AlbumPreview
|
||||
}
|
||||
|
||||
type songResponse struct {
|
||||
@ -117,7 +113,8 @@ func (s *Song) parseSongData(doc *goquery.Document) error {
|
||||
|
||||
func joinNames(data []struct {
|
||||
Name string
|
||||
}) string {
|
||||
},
|
||||
) string {
|
||||
var names []string
|
||||
for _, hasName := range data {
|
||||
names = append(names, hasName.Name)
|
||||
|
70
handlers/artist.go
Normal file
70
handlers/artist.go
Normal file
@ -0,0 +1,70 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rramiachraf/dumb/data"
|
||||
"github.com/rramiachraf/dumb/utils"
|
||||
"github.com/rramiachraf/dumb/views"
|
||||
)
|
||||
|
||||
func artist(l *utils.Logger) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
artistName := mux.Vars(r)["artist"]
|
||||
|
||||
id := fmt.Sprintf("artist:%s", artistName)
|
||||
|
||||
if a, err := getCache[data.Artist](id); err == nil {
|
||||
views.ArtistPage(a).Render(context.Background(), w)
|
||||
return
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://genius.com/artists/%s", artistName)
|
||||
|
||||
resp, err := utils.SendRequest(url)
|
||||
if err != nil {
|
||||
l.Error(err.Error())
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
views.ErrorPage(500, "cannot reach Genius servers").Render(context.Background(), w)
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
views.ErrorPage(404, "page not found").Render(context.Background(), w)
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||
if err != nil {
|
||||
l.Error(err.Error())
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
views.ErrorPage(500, "something went wrong").Render(context.Background(), w)
|
||||
return
|
||||
}
|
||||
|
||||
cf := doc.Find(".cloudflare_content").Length()
|
||||
if cf > 0 {
|
||||
l.Error("cloudflare got in the way")
|
||||
views.ErrorPage(500, "cloudflare is detected").Render(context.Background(), w)
|
||||
return
|
||||
}
|
||||
|
||||
var a data.Artist
|
||||
if err = a.Parse(doc); err != nil {
|
||||
l.Error(err.Error())
|
||||
}
|
||||
|
||||
views.ArtistPage(a).Render(context.Background(), w)
|
||||
|
||||
if err = setCache(id, a); err != nil {
|
||||
l.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
50
handlers/artist_test.go
Normal file
50
handlers/artist_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
|
||||
"github.com/rramiachraf/dumb/utils"
|
||||
)
|
||||
|
||||
func TestArtist(t *testing.T) {
|
||||
url := "/artists/Red-hot-chili-peppers"
|
||||
name := "Red Hot Chili Peppers"
|
||||
firstAlbumName := "Cardiff, Wales: 6/23/04"
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
l := utils.NewLogger(os.Stdout)
|
||||
m := New(l, &assets{})
|
||||
|
||||
m.ServeHTTP(rr, r)
|
||||
|
||||
defer rr.Result().Body.Close()
|
||||
|
||||
if rr.Result().StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected %d, got %d\n", http.StatusOK, rr.Result().StatusCode)
|
||||
}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(rr.Result().Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
artistName := doc.Find("#metadata-info > h1").First().Text()
|
||||
if artistName != name {
|
||||
t.Fatalf("expected %q, got %q\n", name, artistName)
|
||||
}
|
||||
|
||||
albumName := doc.Find("#artist-albumlist > a > p").First().Text()
|
||||
if albumName != firstAlbumName {
|
||||
t.Fatalf("expected %q, got %q\n", firstAlbumName, albumName)
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type cachable interface {
|
||||
data.Album | data.Song | data.Annotation | []byte
|
||||
data.Album | data.Song | data.Annotation | data.Artist | []byte
|
||||
}
|
||||
|
||||
var c, _ = bigcache.New(context.Background(), bigcache.DefaultConfig(time.Hour*24))
|
||||
|
@ -22,6 +22,7 @@ func New(logger *utils.Logger, staticFiles static) *mux.Router {
|
||||
w.Write([]byte("User-agent: *\nDisallow: /\n"))
|
||||
})
|
||||
r.HandleFunc("/albums/{artist}/{albumName}", album(logger)).Methods("GET")
|
||||
r.HandleFunc("/artists/{artist}", artist(logger)).Methods("GET")
|
||||
r.HandleFunc("/images/{filename}.{ext}", imageProxy(logger)).Methods("GET")
|
||||
r.HandleFunc("/search", search(logger)).Methods("GET")
|
||||
r.HandleFunc("/{annotation-id}/{artist-song}/{verse}/annotations", annotations(logger)).Methods("GET")
|
||||
|
25
style/artist.css
Normal file
25
style/artist.css
Normal file
@ -0,0 +1,25 @@
|
||||
artist-albumlist #artist-albumlist p {
|
||||
color: #181d31;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dark #artist-albumlist p {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
#artist-albumlist small {
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dark #artist-albumlist small {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
#metadata p {
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.dark #metadata p {
|
||||
color: #ddd;
|
||||
}
|
@ -132,4 +132,3 @@
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ templ AlbumPage(a data.Album) {
|
||||
<div id="metadata">
|
||||
<img id="album-artwork" src={ data.ExtractImageURL(a.Image) } alt="Album image"/>
|
||||
<div id="metadata-info">
|
||||
<h2>{ a.Artist }</h2>
|
||||
<a href={ templ.URL(a.Artist.URL) } id="album-artist"><h2>{ a.Artist.Name }</h2></a>
|
||||
<h1>{ a.Name }</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
27
views/artist.templ
Normal file
27
views/artist.templ
Normal file
@ -0,0 +1,27 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"github.com/rramiachraf/dumb/data"
|
||||
)
|
||||
|
||||
templ ArtistPage(a data.Artist) {
|
||||
@layout(a.Name) {
|
||||
<div id="container">
|
||||
<div id="metadata">
|
||||
<img id="artist-image" src={ data.ExtractImageURL(a.Image) } alt="Artist image"/>
|
||||
<div id="metadata-info">
|
||||
<h1>{ a.Name }</h1>
|
||||
<p>@templ.Raw(a.Description)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="artist-albumlist">
|
||||
for _, album := range a.Albums {
|
||||
<a href={ templ.URL(album.URL) } id="artist-single-album">
|
||||
<img id="album-image" src={ data.ExtractImageURL(album.Image) } alt="Artist image" />
|
||||
<p>{ album.Name }</p>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user