chore: second attempt to sync branch with latest changes
This commit is contained in:
parent
e536fc8ea5
commit
3e50e52d5e
146
album.go
146
album.go
@ -1,146 +0,0 @@
|
||||
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
|
||||
err := json.Unmarshal([]byte(pageMetadata), &albumMetadataFromPage)
|
||||
if err != nil {
|
||||
logger.Errorln(err)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
err = setCache(id, a)
|
||||
if err != nil {
|
||||
logger.Errorln(err)
|
||||
}
|
||||
|
||||
}
|
131
annotations.go
131
annotations.go
@ -1,131 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type annotationsResponse struct {
|
||||
Response struct {
|
||||
Referent struct {
|
||||
Annotations []struct {
|
||||
Body struct {
|
||||
Html string `json:"html"`
|
||||
} `json:"body"`
|
||||
} `json:"annotations"`
|
||||
} `json:"referent"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
func annotationsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
|
||||
if data, err := getCache(id); err == nil {
|
||||
|
||||
response, err := json.Marshal(data)
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("could not marshal json: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
render("error", w, map[string]string{
|
||||
"Status": "500",
|
||||
"Error": "Could not parse genius api response",
|
||||
})
|
||||
return
|
||||
}
|
||||
w.Header().Set("content-type", "application/json")
|
||||
_, err = w.Write(response)
|
||||
if err != nil {
|
||||
logger.Errorln("Error sending response: ", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://genius.com/api/referents/%s?text_format=html", id)
|
||||
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
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
logger.Errorln("Error paring genius api response", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
render("error", w, map[string]string{
|
||||
"Status": "500",
|
||||
"Error": "Parsing error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var data annotationsResponse
|
||||
err = json.Unmarshal(buf.Bytes(), &data)
|
||||
if err != nil {
|
||||
logger.Errorf("could not unmarshal json: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
render("error", w, map[string]string{
|
||||
"Status": "500",
|
||||
"Error": "Could not parse genius api response",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("content-type", "application/json")
|
||||
body := data.Response.Referent.Annotations[0].Body
|
||||
body.Html = cleanBody(body.Html)
|
||||
response, err := json.Marshal(body)
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("could not marshal json: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
render("error", w, map[string]string{
|
||||
"Status": "500",
|
||||
"Error": "Could not parse genius api response",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = setCache(id, body)
|
||||
if err != nil {
|
||||
logger.Errorln(err)
|
||||
}
|
||||
|
||||
_, err = w.Write(response)
|
||||
if err != nil {
|
||||
logger.Errorln("Error sending response: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanBody(body string) string {
|
||||
var withCleanedImageLinks = strings.Replace(body, "https://images.rapgenius.com/", "/images/", -1)
|
||||
|
||||
var re = regexp.MustCompile(`https?:\/\/[a-z]*.?genius.com`)
|
||||
var withCleanedLinks = re.ReplaceAllString(withCleanedImageLinks, "")
|
||||
|
||||
return withCleanedLinks
|
||||
}
|
1
go.sum
1
go.sum
@ -50,4 +50,3 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
|
176
lyrics.go
176
lyrics.go
@ -1,176 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type song struct {
|
||||
Artist string
|
||||
Title string
|
||||
Image string
|
||||
Lyrics string
|
||||
Credits map[string]string
|
||||
About [2]string
|
||||
Album string
|
||||
LinkToAlbum string
|
||||
}
|
||||
|
||||
type songResponse struct {
|
||||
Response struct {
|
||||
Song struct {
|
||||
ArtistNames string `json:"artist_names"`
|
||||
Image string `json:"song_art_image_thumbnail_url"`
|
||||
Title string
|
||||
Description struct {
|
||||
Plain string
|
||||
}
|
||||
Album struct {
|
||||
Url string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
CustomPerformances []customPerformance `json:"custom_performances"`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type customPerformance struct {
|
||||
Label string
|
||||
Artists []struct {
|
||||
Name string
|
||||
}
|
||||
}
|
||||
|
||||
func (s *song) parseLyrics(doc *goquery.Document) {
|
||||
doc.Find("[data-lyrics-container='true']").Each(func(i int, ss *goquery.Selection) {
|
||||
h, err := ss.Html()
|
||||
if err != nil {
|
||||
logger.Errorln("unable to parse lyrics", err)
|
||||
}
|
||||
s.Lyrics += h
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
logger.Errorln(err)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
var data songResponse
|
||||
decoder := json.NewDecoder(res.Body)
|
||||
err = decoder.Decode(&data)
|
||||
if err != nil {
|
||||
logger.Errorln(err)
|
||||
}
|
||||
|
||||
songData := data.Response.Song
|
||||
s.Artist = songData.ArtistNames
|
||||
s.Image = songData.Image
|
||||
s.Title = songData.Title
|
||||
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
|
||||
for _, artist := range perf.Artists {
|
||||
artists = append(artists, artist.Name)
|
||||
}
|
||||
s.Credits[perf.Label] = strings.Join(artists, ", ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func truncateText(text string) string {
|
||||
textArr := strings.Split(text, "")
|
||||
|
||||
if len(textArr) > 250 {
|
||||
return strings.Join(textArr[0:250], "") + "..."
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
func (s *song) parse(doc *goquery.Document) {
|
||||
s.parseLyrics(doc)
|
||||
s.parseSongData(doc)
|
||||
}
|
||||
|
||||
func lyricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
|
||||
if data, err := getCache(id); err == nil {
|
||||
render("lyrics", w, data)
|
||||
return
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://genius.com/%s-lyrics", id)
|
||||
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 s song
|
||||
s.parse(doc)
|
||||
|
||||
render("lyrics", w, s)
|
||||
err = setCache(id, s)
|
||||
if err != nil {
|
||||
logger.Errorln(err)
|
||||
}
|
||||
|
||||
}
|
33
main.go
33
main.go
@ -1,11 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
<<<<<<< HEAD
|
||||
"context"
|
||||
=======
|
||||
"embed"
|
||||
>>>>>>> upstream/main
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -22,36 +18,7 @@ import (
|
||||
var staticFiles embed.FS
|
||||
|
||||
func main() {
|
||||
<<<<<<< HEAD
|
||||
ctx := context.Background()
|
||||
c, err := bigcache.New(ctx, bigcache.DefaultConfig(time.Hour*24))
|
||||
if err != nil {
|
||||
logger.Fatalln("can't initialize caching")
|
||||
}
|
||||
cache = c
|
||||
|
||||
r := mux.NewRouter()
|
||||
|
||||
r.Use(securityHeaders)
|
||||
|
||||
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { render("home", w, nil) })
|
||||
r.HandleFunc("/search", searchHandler).Methods("GET")
|
||||
r.HandleFunc("/{id}-lyrics", lyricsHandler)
|
||||
r.HandleFunc("/{id}/{artist-song}/{verse}/annotations", annotationsHandler)
|
||||
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{
|
||||
"Status": "404",
|
||||
"Error": "page not found",
|
||||
})
|
||||
|
||||
})
|
||||
=======
|
||||
logger := utils.NewLogger(os.Stdout)
|
||||
>>>>>>> upstream/main
|
||||
|
||||
server := &http.Server{
|
||||
Handler: handlers.New(logger, staticFiles),
|
||||
|
77
proxy.go
77
proxy.go
@ -1,77 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func isValidExt(ext string) bool {
|
||||
valid := []string{"jpg", "jpeg", "png", "gif"}
|
||||
for _, c := range valid {
|
||||
if strings.ToLower(ext) == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func extractURL(image string) string {
|
||||
u, err := url.Parse(image)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("/images%s", u.Path)
|
||||
}
|
||||
|
||||
func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
v := mux.Vars(r)
|
||||
f := v["filename"]
|
||||
ext := v["ext"]
|
||||
|
||||
if !isValidExt(ext) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render("error", w, map[string]string{
|
||||
"Status": "400",
|
||||
"Error": "Something went wrong",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// first segment of URL resize the image to reduce bandwith usage.
|
||||
url := fmt.Sprintf("https://t2.genius.com/unsafe/300x300/https://images.genius.com/%s.%s", f, ext)
|
||||
|
||||
res, 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
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
render("error", w, map[string]string{
|
||||
"Status": "500",
|
||||
"Error": "something went wrong",
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-type", fmt.Sprintf("image/%s", ext))
|
||||
_, err = io.Copy(w, res.Body)
|
||||
if err != nil {
|
||||
logger.Errorln(err)
|
||||
}
|
||||
|
||||
}
|
64
search.go
64
search.go
@ -1,64 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type response struct {
|
||||
Response struct {
|
||||
Sections sections
|
||||
}
|
||||
}
|
||||
|
||||
type result struct {
|
||||
ArtistNames string `json:"artist_names"`
|
||||
Title string
|
||||
Path string
|
||||
Thumbnail string `json:"song_art_image_thumbnail_url"`
|
||||
}
|
||||
|
||||
type hits []struct {
|
||||
Result result
|
||||
}
|
||||
|
||||
type sections []struct {
|
||||
Type string
|
||||
Hits hits
|
||||
}
|
||||
|
||||
type renderVars struct {
|
||||
Query string
|
||||
Sections sections
|
||||
}
|
||||
|
||||
func searchHandler(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query().Get("q")
|
||||
url := fmt.Sprintf(`https://genius.com/api/search/multi?q=%s`, url.QueryEscape(query))
|
||||
|
||||
res, 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",
|
||||
})
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
var data response
|
||||
|
||||
d := json.NewDecoder(res.Body)
|
||||
err = d.Decode(&data)
|
||||
if err != nil {
|
||||
logger.Errorln(err)
|
||||
}
|
||||
|
||||
vars := renderVars{query, data.Response.Sections}
|
||||
|
||||
render("search", w, vars)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user