diff --git a/lyrics.go b/lyrics.go index 4f95857..26a8629 100644 --- a/lyrics.go +++ b/lyrics.go @@ -3,7 +3,6 @@ package main import ( "fmt" "net/http" - "net/url" "strings" "github.com/PuerkitoBio/goquery" @@ -34,9 +33,7 @@ func (s *song) parseMetadata(doc *goquery.Document) { title := doc.Find("h1[class*='Title']").First().Text() image, exists := doc.Find("meta[property='og:image']").Attr("content") if exists { - if u, err := url.Parse(image); err == nil { - s.Image = fmt.Sprintf("/images%s", u.Path) - } + s.Image = extractURL(image) } s.Title = title diff --git a/main.go b/main.go index 7363f74..f23f593 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ func main() { 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("/images/{filename}.{ext}", proxyHandler) r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) diff --git a/proxy.go b/proxy.go index c10216d..dc9afcd 100644 --- a/proxy.go +++ b/proxy.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strings" "github.com/gorilla/mux" @@ -20,6 +21,15 @@ func isValidExt(ext string) bool { 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"] diff --git a/search.go b/search.go new file mode 100644 index 0000000..73e71f5 --- /dev/null +++ b/search.go @@ -0,0 +1,60 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" +) + +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`, query) + + res, err := http.Get(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) + d.Decode(&data) + + vars := renderVars{query, data.Response.Sections} + + render("search", w, vars) +} diff --git a/static/style.css b/static/style.css index 6d92c5e..4b0ded4 100644 --- a/static/style.css +++ b/static/style.css @@ -247,6 +247,70 @@ footer a:hover { color: #222; } +.main { + flex-grow: 1; +} + +#search-page { + display: flex; + flex-direction: column; + gap: 3rem; + padding: 2rem 1rem; +} + +@media screen and (min-width: 800px) { + #search-page { + width: 80rem; + margin: 0 auto; + } +} + +#search-input { + width: 100%; + padding: 1rem 2rem; + box-sizing: border-box; + border-radius: 5px; + border: 1px solid #ddd; + color: #222; +} + +#search-results { + display: flex; + flex-direction: column; + gap: 1rem; +} + +#search-results h1 { + text-align:center; + color: #111; + font-size: 2.5rem; +} + +#search-item { + display: flex; + height: 8rem; + border: 1px solid #eee; + border-radius: 5px; + gap: 1rem; + padding: 1rem; + box-shadow: 0 1px 1px #ddd; +} + +#search-item h2 { + font-size: 1.8rem; + color: #222; +} + +#search-item span { + font-size: 1.3rem; + color: #333; +} + +#search-item img { + width: 8rem; + border-radius: 5px; +} + /* dark mode */ @media (prefers-color-scheme: dark) { body { @@ -287,4 +351,24 @@ footer a:hover { #home p, #error p{ color: #ddd; } + + #search-input { + background-color: #ddd; + } + + #search-page h1 { + color: #eee; + } + + #search-item { + border: 1px solid #888; + } + + #search-item h2 { + color: #ddd; + } + + #search-item span { + color: #bbb; + } } diff --git a/utils.go b/utils.go index 15f89d9..7df3d38 100644 --- a/utils.go +++ b/utils.go @@ -62,7 +62,8 @@ func getTemplates(templates ...string) []string { func render(n string, w http.ResponseWriter, data interface{}) { w.Header().Set("content-type", "text/html") - t, err := template.ParseFiles(getTemplates(n, "navbar", "footer")...) + t := template.New(n + ".tmpl").Funcs(template.FuncMap{"extractURL": extractURL}) + t, err := t.ParseFiles(getTemplates(n, "navbar", "footer")...) if err != nil { logger.Errorln(err) w.WriteHeader(http.StatusInternalServerError) diff --git a/views/home.tmpl b/views/home.tmpl index 5af7fde..47d1a20 100644 --- a/views/home.tmpl +++ b/views/home.tmpl @@ -14,7 +14,10 @@

Welcome to dumb

An alternative frontend for genius.com

- Just redirect Genius URLs to this instance and It's all good. +
+ +
+ {{template "footer"}} diff --git a/views/search.tmpl b/views/search.tmpl new file mode 100644 index 0000000..87ea9a3 --- /dev/null +++ b/views/search.tmpl @@ -0,0 +1,36 @@ + + + + Search - dumb + + + + + +
+ {{template "navbar"}} +
+
+ +
+
+ {{range .Sections}} + {{if eq .Type "song"}} +

Songs

+ {{range .Hits}} + + +
+ {{.Result.ArtistNames}} +

{{.Result.Title}}

+
+
+ {{end}} + {{end}} + {{end}} +
+
+ {{template "footer"}} + + +