feat: add search functionality
This commit is contained in:
parent
b7c91e7f1b
commit
dd0ee8723b
@ -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
|
||||
|
1
main.go
1
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"))))
|
||||
|
10
proxy.go
10
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"]
|
||||
|
60
search.go
Normal file
60
search.go
Normal file
@ -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)
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
3
utils.go
3
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)
|
||||
|
@ -14,7 +14,10 @@
|
||||
<h1>Welcome to dumb</h1>
|
||||
<p>An alternative frontend for genius.com</p>
|
||||
</div>
|
||||
<code>Just redirect Genius URLs to this instance and It's all good.</code>
|
||||
<form method="GET" action="/search">
|
||||
<input type="text" name="q" id="search-input" placeholder="Search..." />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
{{template "footer"}}
|
||||
</div>
|
||||
|
36
views/search.tmpl
Normal file
36
views/search.tmpl
Normal file
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Search - dumb</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
<main id="app">
|
||||
{{template "navbar"}}
|
||||
<div id="search-page" class="main">
|
||||
<form method="GET">
|
||||
<input type="text" name="q" id="search-input" placeholder="Search..." value="{{.Query}}" />
|
||||
</form>
|
||||
<div id="search-results">
|
||||
{{range .Sections}}
|
||||
{{if eq .Type "song"}}
|
||||
<h1>Songs</h1>
|
||||
{{range .Hits}}
|
||||
<a id="search-item" href="{{.Result.Path}}">
|
||||
<img src="{{extractURL .Result.Thumbnail}}"/>
|
||||
<div>
|
||||
<span>{{.Result.ArtistNames}}</span>
|
||||
<h2>{{.Result.Title}}</h2>
|
||||
</div>
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "footer"}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user