Merge branch 'main' into instances-list-json

This commit is contained in:
Achraf RRAMI 2024-03-04 21:47:01 +00:00 committed by GitHub
commit 6ead4ced4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 1350 additions and 964 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
dumb
views/*_templ.go

View File

@ -6,7 +6,7 @@ COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build
RUN make build
EXPOSE 5555/tcp

4
Makefile Normal file
View File

@ -0,0 +1,4 @@
gentempl:
@command -v templ &> /dev/null || go install github.com/a-h/templ/cmd/templ@latest
build:gentempl
templ generate && go build -o dumb

View File

@ -1,5 +1,5 @@
# dumb
With the massive daily increase of useless scripts on Genius's web frontend and having to download megabytes of clutter, [dumb](https://github.com/rramiachraf/dumb) tries to make reading lyrics from Genius a pleasant experience and as lightweight as possible.
With the massive daily increase of useless scripts on Genius's web frontend, and having to download megabytes of clutter, [dumb](https://github.com/rramiachraf/dumb) tries to make reading lyrics from Genius a pleasant experience, and as lightweight as possible.
<a href="https://codeberg.org/rramiachraf/dumb"><img src="https://img.shields.io/badge/Codeberg-%232185d0" /></a>
@ -10,7 +10,7 @@ With the massive daily increase of useless scripts on Genius's web frontend and
```bash
git clone https://github.com/rramiachraf/dumb
cd dumb
go build
make build
./dumb
```
@ -38,9 +38,8 @@ The default port is 5555, you can use other ports by setting the `PORT` environm
| --- | --- |
| <http://vernxpcpqi2y4uhu7to4rnjmyjjgzh3x3qxyzpmkhykefchkmleq.b32.i2p> | https://vern.cc |
Instances list in JSON format can be found in [instances.json](instances.json) file.
For people who might be capable and interested in hosting a public instance feel free to do so and don't forget to open a pull request so your instance can be included here.
- Instances list in JSON format can be found in [instances.json](instances.json) file.
- For people who might be capable and interested in hosting a public instance feel free to do so, and don't forget to open a pull request, so your instance can be included here.
## Contributing
Contributions are welcome.

139
album.go
View File

@ -1,139 +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
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)
}

View File

@ -1,127 +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
}
setCache(id, body)
_, 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
}

77
data/album.go Normal file
View File

@ -0,0 +1,77 @@
package data
import (
"encoding/json"
"strings"
"github.com/PuerkitoBio/goquery"
)
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) error {
pageMetadata, exists := doc.Find("meta[itemprop='page_data']").Attr("content")
if !exists {
return nil
}
var albumMetadataFromPage albumMetadata
if err := json.Unmarshal([]byte(pageMetadata), &albumMetadataFromPage); err != nil {
return 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)
a.About[1] = ""
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})
}
return nil
}
func (a *Album) Parse(doc *goquery.Document) error {
return a.parseAlbumData(doc)
}

15
data/annotation.go Normal file
View File

@ -0,0 +1,15 @@
package data
type AnnotationsResponse struct {
Response struct {
Referent struct {
Annotations []struct {
Body Annotation `json:"body"`
} `json:"annotations"`
} `json:"referent"`
} `json:"response"`
}
type Annotation struct {
HTML string `json:"html"`
}

117
data/lyrics.go Normal file
View File

@ -0,0 +1,117 @@
package data
import (
"encoding/json"
"fmt"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/sirupsen/logrus"
)
type Song struct {
Artist string
Title string
Image string
Lyrics string
Credits map[string]string
About [2]string
Album struct {
URL string
Name string
Image 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"`
Image string `json:"cover_art_url"`
}
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 {
logrus.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 {
logrus.Errorln(err)
}
defer res.Body.Close()
var data songResponse
decoder := json.NewDecoder(res.Body)
err = decoder.Decode(&data)
if err != nil {
logrus.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.Name = songData.Album.Name
s.Album.URL = strings.Replace(songData.Album.URL, "https://genius.com", "", -1)
s.Album.Image = ExtractImageURL(songData.Album.Image)
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)
}

16
data/proxy.go Normal file
View File

@ -0,0 +1,16 @@
package data
import (
"fmt"
"net/url"
)
func ExtractImageURL(image string) string {
u, err := url.Parse(image)
if err != nil {
return ""
}
return fmt.Sprintf("/images%s", u.Path)
}

28
data/search.go Normal file
View File

@ -0,0 +1,28 @@
package data
type SearchResponse 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 SearchResults struct {
Query string
Sections sections
}

46
data/utils.go Normal file
View File

@ -0,0 +1,46 @@
package data
import (
"net"
"net/http"
"net/url"
"time"
"github.com/caffix/cloudflare-roundtripper/cfrt"
)
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"
var client = &http.Client{
Timeout: 20 * time.Second,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 15 * time.Second,
KeepAlive: 15 * time.Second,
DualStack: true,
}).DialContext,
},
}
func sendRequest(u string) (*http.Response, error) {
url, err := url.Parse(u)
if err != nil {
return nil, err
}
client.Transport, err = cfrt.New(client.Transport)
if err != nil {
return nil, err
}
req := &http.Request{
Method: http.MethodGet,
URL: url,
Header: map[string][]string{
"Accept-Language": {"en-US"},
"User-Agent": {UA},
},
}
return client.Do(req)
}

25
go.mod
View File

@ -1,20 +1,33 @@
module github.com/rramiachraf/dumb
go 1.18
go 1.21
toolchain go1.22.0
require (
github.com/PuerkitoBio/goquery v1.8.0
github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.26
github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1
github.com/PuerkitoBio/goquery v1.8.1
github.com/a-h/templ v0.2.598
github.com/allegro/bigcache/v3 v3.0.2
github.com/caffix/cloudflare-roundtripper v0.0.0-20181218223503-4c29d231c9cb
github.com/gorilla/mux v1.8.0
github.com/sirupsen/logrus v1.9.3
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/caffix/cloudflare-roundtripper v0.0.0-20181218223503-4c29d231c9cb // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/klauspost/compress v1.17.6 // indirect
github.com/quic-go/quic-go v0.41.0 // indirect
github.com/refraction-networking/utls v1.6.2 // indirect
github.com/robertkrimen/otto v0.2.1 // indirect
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
h12.io/socks v1.0.3 // indirect
)

404
go.sum
View File

@ -1,43 +1,425 @@
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.26 h1:6fexoGmvzoXMSk14BZ0AirapVm5c3KUsEjE0jLlVKi8=
github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.26/go.mod h1:QFi/EVO7qqru3Ftxz1LR+96jIc91Tifv0DnskF/gWQ8=
github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1 h1:/lqhaiz7xdPr6kuaW1tQ/8DdpWdxkdyd9W/6EHz4oRw=
github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1/go.mod h1:Hvab/V/YKCDXsEpKYKHjAXH5IFOmoq9FsfxjztEqvDc=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/a-h/templ v0.2.598 h1:6jMIHv6wQZvdPxTuv87erW4RqN/FPU0wk7ZHN5wVuuo=
github.com/a-h/templ v0.2.598/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8=
github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI=
github.com/allegro/bigcache/v3 v3.0.2/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/caffix/cloudflare-roundtripper v0.0.0-20181218223503-4c29d231c9cb h1:RKySaWkjoE/ECY1FFk4JbcfG9dTrJmoqp08rQ2oA51Y=
github.com/caffix/cloudflare-roundtripper v0.0.0-20181218223503-4c29d231c9cb/go.mod h1:LkIRP8n1KY5Ew4Y7S+V7ooavIXNrraFZ1IKmI4SNMuE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc=
github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw=
github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=
github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
github.com/refraction-networking/utls v1.6.2 h1:iTeeGY0o6nMNcGyirxkD5bFIsVctP5InGZ3E0HrzS7k=
github.com/refraction-networking/utls v1.6.2/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

71
handlers/album.go Normal file
View File

@ -0,0 +1,71 @@
package handlers
import (
"context"
"fmt"
"net/http"
"github.com/PuerkitoBio/goquery"
"github.com/gorilla/mux"
"github.com/rramiachraf/dumb/data"
"github.com/rramiachraf/dumb/views"
"github.com/sirupsen/logrus"
)
func Album(l *logrus.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
artist := mux.Vars(r)["artist"]
albumName := mux.Vars(r)["albumName"]
id := fmt.Sprintf("%s/%s", artist, albumName)
if a, err := getCache[data.Album](id); err == nil {
views.AlbumPage(a).Render(context.Background(), w)
return
}
url := fmt.Sprintf("https://genius.com/albums/%s/%s", artist, albumName)
resp, err := sendRequest(url)
if err != nil {
l.Errorln(err)
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.Errorln(err)
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.Errorln("cloudflare got in the way")
views.ErrorPage(500, "i'll fix this later #21").Render(context.Background(), w)
return
}
var a data.Album
if err = a.Parse(doc); err != nil {
l.Error(err)
}
views.AlbumPage(a).Render(context.Background(), w)
if err = setCache(id, a); err != nil {
l.Errorln(err)
}
}
}

93
handlers/annotations.go Normal file
View File

@ -0,0 +1,93 @@
package handlers
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"github.com/gorilla/mux"
"github.com/rramiachraf/dumb/data"
"github.com/rramiachraf/dumb/views"
"github.com/sirupsen/logrus"
)
func Annotations(l *logrus.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
if a, err := getCache[data.Annotation]("annotation:" + id); err == nil {
encoder := json.NewEncoder(w)
w.Header().Set("content-type", "application/json")
if err = encoder.Encode(&a); err != nil {
l.Errorln(err)
}
return
}
url := fmt.Sprintf("https://genius.com/api/referents/%s?text_format=html", id)
resp, err := sendRequest(url)
if err != nil {
l.Errorln(err)
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
}
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)
if err != nil {
l.Errorln("Error paring genius api response", err)
w.WriteHeader(http.StatusInternalServerError)
views.ErrorPage(500, "something went wrong").Render(context.Background(), w)
return
}
var data data.AnnotationsResponse
err = json.Unmarshal(buf.Bytes(), &data)
if err != nil {
l.Errorf("could not unmarshal json: %s\n", err)
w.WriteHeader(http.StatusInternalServerError)
views.ErrorPage(500, "something went wrong").Render(context.Background(), w)
return
}
body := data.Response.Referent.Annotations[0].Body
body.HTML = cleanBody(body.HTML)
w.Header().Set("content-type", "application/json")
encoder := json.NewEncoder(w)
if err = encoder.Encode(&body); err != nil {
l.Errorln("Error sending response: ", err)
return
}
if err = setCache("annotation:"+id, body); err != nil {
l.Errorln(err)
}
}
}
func cleanBody(body string) string {
var withCleanedImageLinks = strings.ReplaceAll(body, "https://images.rapgenius.com/", "/images/")
withCleanedImageLinks = strings.ReplaceAll(body, "https://images.genius.com/", "/images/")
var re = regexp.MustCompile(`https?:\/\/[a-z]*.?genius.com`)
var withCleanedLinks = re.ReplaceAllString(withCleanedImageLinks, "")
return withCleanedLinks
}

39
handlers/cache.go Normal file
View File

@ -0,0 +1,39 @@
package handlers
import (
"encoding/json"
"time"
"github.com/allegro/bigcache/v3"
"github.com/rramiachraf/dumb/data"
)
type cachable interface {
data.Album | data.Song | data.Annotation
}
var c, _ = bigcache.NewBigCache(bigcache.DefaultConfig(time.Hour * 24))
func setCache(key string, entry interface{}) error {
data, err := json.Marshal(&entry)
if err != nil {
return err
}
return c.Set(key, data)
}
func getCache[v cachable](key string) (v, error) {
var decoded v
data, err := c.Get(key)
if err != nil {
return decoded, err
}
if err = json.Unmarshal(data, &decoded); err != nil {
return decoded, err
}
return decoded, nil
}

64
handlers/lyrics.go Normal file
View File

@ -0,0 +1,64 @@
package handlers
import (
"context"
"fmt"
"net/http"
"github.com/PuerkitoBio/goquery"
"github.com/gorilla/mux"
"github.com/rramiachraf/dumb/data"
"github.com/rramiachraf/dumb/views"
"github.com/sirupsen/logrus"
)
func Lyrics(l *logrus.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
if s, err := getCache[data.Song](id); err == nil {
views.LyricsPage(s).Render(context.Background(), w)
return
}
url := fmt.Sprintf("https://genius.com/%s-lyrics", id)
resp, err := sendRequest(url)
if err != nil {
l.Errorln(err)
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.Errorln(err)
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.Errorln("cloudflare got in the way")
views.ErrorPage(500, "TODO: fix Cloudflare #21").Render(context.Background(), w)
return
}
var s data.Song
s.Parse(doc)
views.LyricsPage(s).Render(context.Background(), w)
if err = setCache(id, s); err != nil {
l.Errorln(err)
}
}
}

60
handlers/proxy.go Normal file
View File

@ -0,0 +1,60 @@
package handlers
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/rramiachraf/dumb/views"
"github.com/sirupsen/logrus"
)
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 ImageProxy(l *logrus.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
v := mux.Vars(r)
f := v["filename"]
ext := v["ext"]
if !isValidExt(ext) {
w.WriteHeader(http.StatusBadRequest)
views.ErrorPage(400, "something went wrong").Render(context.Background(), w)
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 {
l.Errorln(err)
w.WriteHeader(http.StatusInternalServerError)
views.ErrorPage(500, "cannot reach Genius servers").Render(context.Background(), w)
return
}
if res.StatusCode != http.StatusOK {
w.WriteHeader(http.StatusInternalServerError)
views.ErrorPage(500, "something went wrong").Render(context.Background(), w)
return
}
w.Header().Add("Content-type", fmt.Sprintf("image/%s", ext))
if _, err = io.Copy(w, res.Body); err != nil {
l.Errorln("unable to write image", err)
}
}
}

44
handlers/search.go Normal file
View File

@ -0,0 +1,44 @@
package handlers
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/rramiachraf/dumb/data"
"github.com/rramiachraf/dumb/views"
"github.com/sirupsen/logrus"
)
func Search(l *logrus.Logger) http.HandlerFunc {
return func(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 {
l.Errorln(err)
w.WriteHeader(http.StatusInternalServerError)
views.ErrorPage(500, "cannot reach Genius servers").Render(context.Background(), w)
return
}
defer res.Body.Close()
var sRes data.SearchResponse
d := json.NewDecoder(res.Body)
if err = d.Decode(&sRes); err != nil {
l.Errorln(err)
w.WriteHeader(http.StatusInternalServerError)
views.ErrorPage(500, "something went wrong").Render(context.Background(), w)
}
results := data.SearchResults{Query: query, Sections: sRes.Response.Sections}
views.SearchPage(results).Render(context.Background(), w)
}
}

42
handlers/utils.go Normal file
View File

@ -0,0 +1,42 @@
package handlers
import (
"net/http"
"net/url"
"time"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
fhttp "github.com/Danny-Dasilva/fhttp"
)
func MustHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
csp := "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; object-src 'none'"
w.Header().Add("content-security-policy", csp)
w.Header().Add("referrer-policy", "no-referrer")
w.Header().Add("x-content-type-options", "nosniff")
next.ServeHTTP(w, r)
})
}
const UA = "Mozilla/5.0 (Windows NT 10.0; rv:123.0) Gecko/20100101 Firefox/123.0"
const JA3 = "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,0-23-65281-10-11-16-5-34-51-43-13-45-28-65037-41,29-23-24-25-256-257,0"
func sendRequest(u string) (*fhttp.Response, error) {
url, err := url.Parse(u)
if err != nil {
return nil, err
}
client := &fhttp.Client{
Transport: cycletls.NewTransport(JA3, UA),
Timeout: 20 * time.Second,
}
req := &fhttp.Request{
Method: http.MethodGet,
URL: url,
}
return client.Do(req)
}

172
lyrics.go
View File

@ -1,172 +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)
setCache(id, s)
}

31
main.go
View File

@ -1,6 +1,7 @@
package main
import (
"context"
"fmt"
"net"
"net/http"
@ -8,38 +9,30 @@ import (
"strconv"
"time"
"github.com/allegro/bigcache/v3"
"github.com/a-h/templ"
"github.com/gorilla/mux"
"github.com/rramiachraf/dumb/handlers"
"github.com/rramiachraf/dumb/views"
"github.com/sirupsen/logrus"
)
var logger = logrus.New()
func main() {
c, err := bigcache.NewBigCache(bigcache.DefaultConfig(time.Hour * 24))
if err != nil {
logger.Fatalln("can't initialize caching")
}
cache = c
r := mux.NewRouter()
r.Use(securityHeaders)
r.Use(handlers.MustHeaders)
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.Handle("/", templ.Handler(views.HomePage()))
r.HandleFunc("/{id}-lyrics", handlers.Lyrics(logger)).Methods("GET")
r.HandleFunc("/albums/{artist}/{albumName}", handlers.Album(logger)).Methods("GET")
r.HandleFunc("/images/{filename}.{ext}", handlers.ImageProxy(logger)).Methods("GET")
r.HandleFunc("/search", handlers.Search(logger)).Methods("GET")
r.HandleFunc("/{id}/{artist-song}/{verse}/annotations", handlers.Annotations(logger)).Methods("GET")
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",
})
views.ErrorPage(404, "page not found").Render(context.Background(), w)
})
server := &http.Server{

View File

@ -1,73 +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))
io.Copy(w, res.Body)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 402 KiB

View File

@ -1,21 +0,0 @@
[Unit]
Description=ListMonk
Documentation=https://github.com/rramiachraf/dumb
After=system.slice multi-user.target postgresql.service network.target
[Service]
User=git
Type=simple
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=listmonk
WorkingDirectory=/etc/dumb
ExecStart=/etc/dumb/dumb
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

View File

@ -1,35 +0,0 @@
server {
# root /var/www/dumb.yoursite.com/html;
# index index.html index.htm index.nginx-debian.html;
server_name dumb.yoursite.com;
# www.dumb.yoursite.com;
location / {
try_files $uri $uri/ =404;
proxy_pass http://localhost:5555;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/dumb.yoursite.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/dumb.yoursite.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = dumb.yoursite.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
server_name dumb.yoursite.com;
listen 80;
return 404; # managed by Certbot
}

View File

@ -1,61 +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)
d.Decode(&data)
vars := renderVars{query, data.Response.Sections}
render("search", w, vars)
}

View File

@ -14,6 +14,7 @@ document.querySelectorAll("#lyrics a").forEach(item => {
function getAnnotation(e) {
e.preventDefault()
//document.querySelector('.annotation')?.remove()
const uri = e.target.parentElement.getAttribute("href")
const presentAnnotation = document.getElementById(uri)
if (presentAnnotation) {

View File

@ -53,7 +53,6 @@ body {
#lyrics a {
color: inherit;
cursor: initial;
}
#lyrics a, .annotation {
@ -84,7 +83,7 @@ a {
#metadata {
display: flex;
flex-direction: column;
gap: 0.5rem;
gap: 2rem;
flex-basis: 0;
}
@ -99,17 +98,26 @@ a {
font-weight: 500;
}
#metadata-info {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
#album-artwork {
width: 20rem;
width: 24rem;
border-radius: 3px;
box-shadow: 0 1px 1px #ddd;
max-width: 200px;
}
#container {
display: flex;
padding: 5rem 10rem;
gap: 5rem;
display: grid;
padding: 5rem 0;
grid-template-columns: 24rem 1fr 24rem;
width: 1024px;
margin: 0 auto;
gap: 4rem;
flex: 1;
}
#credits {
@ -129,6 +137,12 @@ a {
color: #1e1e1e;
}
#lyrics-album-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
#album-tracklist{
display: flex;
flex-direction: column;
@ -214,8 +228,10 @@ a {
@media screen and (max-width: 900px) {
#container {
padding: 3rem 2rem;
display: flex;
flex-direction: column;
gap: 3rem;
width: calc(100vw - 4rem);;
}
#metadata {

119
utils.go
View File

@ -1,119 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"path"
"text/template"
"time"
"github.com/allegro/bigcache/v3"
"github.com/caffix/cloudflare-roundtripper/cfrt"
)
var cache *bigcache.BigCache
func setCache(key string, entry interface{}) error {
data, err := json.Marshal(&entry)
if err != nil {
return err
}
return cache.Set(key, data)
}
func getCache(key string) (interface{}, error) {
data, err := cache.Get(key)
if err != nil {
return nil, err
}
var decoded interface{}
if err = json.Unmarshal(data, &decoded); err != nil {
return nil, err
}
return decoded, nil
}
func write(w http.ResponseWriter, status int, data []byte) {
w.WriteHeader(status)
w.Write(data)
}
func securityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
csp := "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; object-src 'none'"
w.Header().Add("content-security-policy", csp)
w.Header().Add("referrer-policy", "no-referrer")
w.Header().Add("x-content-type-options", "nosniff")
next.ServeHTTP(w, r)
})
}
func getTemplates(templates ...string) []string {
var pths []string
for _, t := range templates {
tmpl := path.Join("views", fmt.Sprintf("%s.tmpl", t))
pths = append(pths, tmpl)
}
return pths
}
func render(n string, w http.ResponseWriter, data interface{}) {
w.Header().Set("content-type", "text/html")
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)
return
}
if err = t.Execute(w, data); err != nil {
logger.Errorln(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"
var client = &http.Client{
Timeout: 20 * time.Second,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 15 * time.Second,
KeepAlive: 15 * time.Second,
DualStack: true,
}).DialContext,
},
}
func sendRequest(u string) (*http.Response, error) {
url, err := url.Parse(u)
if err != nil {
return nil, err
}
client.Transport, err = cfrt.New(client.Transport)
if err != nil {
return nil, err
}
req := &http.Request{
Method: http.MethodGet,
URL: url,
Header: map[string][]string{
"Accept-Language": {"en-US"},
"User-Agent": {UA},
},
}
return client.Do(req)
}

32
views/album.templ Normal file
View File

@ -0,0 +1,32 @@
package views
import (
"fmt"
"github.com/rramiachraf/dumb/data"
)
templ AlbumPage(a data.Album) {
@layout(fmt.Sprintf("%s - %s", a.Artist, a.Name)) {
<div id="container">
<div id="metadata">
<img id="album-artwork" src={ data.ExtractImageURL(a.Image) }/>
<h2>{ a.Artist }</h2>
<h1>{ a.Name }</h1>
</div>
<div id="album-tracklist">
for _, t := range a.Tracks {
<a href={ templ.URL(t.Url) }>
<p>{ t.Title }</p>
</a>
}
</div>
<div id="info">
<div id="about">
<h1 id="title">About</h1>
<p class="hidden" id="full_about">{ a.About[0] }</p>
<p id="summary">{ a.About[1] }</p>
</div>
</div>
</div>
}
}

View File

@ -1,35 +0,0 @@
<!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>

12
views/error.templ Normal file
View File

@ -0,0 +1,12 @@
package views
import "strconv"
templ ErrorPage(code int, display string) {
@layout("Error - dumb") {
<div id="error">
<h1>{ strconv.Itoa(code) }</h1>
<p>{ display }</p>
</div>
}
}

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>dumb</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="/static/style.css" />
<link rel="icon" href="/static/logo.svg" type="image/svg+xml">
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<main id="app">
{{template "navbar"}}
<div id="error">
<h1>{{.Status}}</h1>
<p>{{.Error}}</p>
</div>
{{template "footer"}}
</div>
</body>
</html>

7
views/footer.templ Normal file
View File

@ -0,0 +1,7 @@
package views
templ footer() {
<footer>
<a rel="noopener noreferrer" target="_blank" href="https://github.com/rramiachraf/dumb">Source Code</a>
</footer>
}

View File

@ -1,5 +0,0 @@
{{define "footer"}}
<footer>
<a rel="noopener noreferrer" target="_blank" href="https://github.com/rramiachraf/dumb">Source Code</a>
</footer>
{{end}}

12
views/head.templ Normal file
View File

@ -0,0 +1,12 @@
package views
templ head(title string) {
<head>
<title>{ title }</title>
<meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="/static/style.css"/>
<link rel="icon" href="/static/logo.svg" type="image/svg+xml"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<script type="text/javascript" src="/static/script.js" defer></script>
</head>
}

15
views/home.templ Normal file
View File

@ -0,0 +1,15 @@
package views
templ HomePage() {
@layout("dumb") {
<div id="home">
<div>
<h1>Welcome to dumb</h1>
<p>An alternative frontend for genius.com</p>
</div>
<form method="GET" action="/search">
<input type="text" name="q" id="search-input" placeholder="Search..."/>
</form>
</div>
}
}

View File

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>dumb</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="/static/style.css" />
<link rel="icon" href="/static/logo.svg" type="image/svg+xml">
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<main id="app">
{{template "navbar"}}
<div id="home">
<div>
<h1>Welcome to dumb</h1>
<p>An alternative frontend for genius.com</p>
</div>
<form method="GET" action="/search">
<input type="text" name="q" id="search-input" placeholder="Search..." />
</form>
</div>
{{template "footer"}}
</div>
</body>
</html>

15
views/layout.templ Normal file
View File

@ -0,0 +1,15 @@
package views
templ layout(title string) {
<!DOCTYPE html>
<html>
@head(title)
<body>
<main id="app">
@navbar()
{ children... }
@footer()
</main>
</body>
</html>
}

51
views/lyrics.templ Normal file
View File

@ -0,0 +1,51 @@
package views
import (
"fmt"
"github.com/rramiachraf/dumb/data"
)
templ LyricsPage(s data.Song) {
@layout(fmt.Sprintf("%s - %s lyrics", s.Artist, s.Title)) {
<div id="container">
<div id="metadata">
<img id="album-artwork" src={ data.ExtractImageURL(s.Image) }/>
<div id="metadata-info">
<h2>{ s.Artist }</h2>
<h1>{ s.Title }</h1>
</div>
</div>
<div id="lyrics">
@templ.Raw(s.Lyrics)
</div>
<div id="info">
if s.About[0] != "" {
<div id="about">
<h1 id="title">About</h1>
<p class="hidden" id="full_about">{ s.About[0] }</p>
<p id="summary">{ s.About[1] }</p>
</div>
}
if len(s.Credits) > 0 {
<div id="credits">
<h1 id="title">Credits</h1>
for key, val := range s.Credits {
<details>
<summary>{ key }</summary>
<p>{ val }</p>
</details>
}
</div>
}
if s.Album.Name != "" {
<div id="lyrics-album-container">
<h1 id="title">{ s.Album.Name }</h1>
<a href={ templ.URL(s.Album.URL) }>
<img title={ "Album: " + s.Album.Name } id="album-artwork" src={ s.Album.Image }/>
</a>
</div>
}
</div>
</div>
}
}

View File

@ -1,40 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>{{.Artist}} - {{.Title}} lyrics</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" />
<link rel="icon" href="/static/logo.svg" type="image/svg+xml">
<script type="text/javascript" src="/static/script.js" defer></script>
</head>
<body>
{{template "navbar"}}
<div id="container">
<div id="metadata">
<a href="{{.LinkToAlbum}}"><img id="album-artwork" src="{{extractURL .Image}}"/></a>
<h2>{{.Artist}}</h2>
<h1>{{.Title}}</h1>
<a href="{{.LinkToAlbum}}"><h2>{{.Album}}</h2></a>
</div>
<div id="lyrics">{{.Lyrics}}</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 id="credits">
<h1 id="title">Credits</h1>
{{range $key, $val := .Credits}}
<details>
<summary>{{$key}}</summary>
<p>{{$val}}</p>
</details>
{{end}}
</div>
</div>
</div>
{{template "footer"}}
</body>
</html>

7
views/navbar.templ Normal file
View File

@ -0,0 +1,7 @@
package views
templ navbar() {
<nav>
<a href="/"><img src="/static/logo.svg"/></a>
</nav>
}

View File

@ -1,5 +0,0 @@
{{define "navbar"}}
<nav>
<a href="/"><img src="/static/logo.svg" /></a>
</nav>
{{end}}

29
views/search.templ Normal file
View File

@ -0,0 +1,29 @@
package views
import "github.com/rramiachraf/dumb/data"
templ SearchPage(r data.SearchResults) {
@layout("Search - dumb") {
<div id="search-page" class="main">
<form method="GET">
<input type="text" name="q" id="search-input" placeholder="Search..." value={ r.Query }/>
</form>
<div id="search-results">
for _, s := range r.Sections {
if s.Type == "song" {
<h1>Songs</h1>
for _, s := range s.Hits {
<a id="search-item" href={ templ.URL(s.Result.Path) }>
<img src={ data.ExtractImageURL(s.Result.Thumbnail) }/>
<div>
<span>{ s.Result.ArtistNames }</span>
<h2>{ s.Result.Title }</h2>
</div>
</a>
}
}
}
</div>
</div>
}
}

View File

@ -1,37 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Search - dumb</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="/static/style.css" />
<link rel="icon" href="/static/logo.svg" type="image/svg+xml">
<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>