small pixel drawing of a pufferfish vore

add "finger" feature, kill "discover"
Jes Olson j3s@c3f.net
Tue, 25 Feb 2025 01:11:50 -0500
commit

0a75b82e3ca65900647e44452c083e898b12c8ee

parent

ef317eafb65f0ba423547bc4d40900f1e8e88418

8 files changed, 126 insertions(+), 29 deletions(-)

jump to
D files/discover.tmpl.html

@@ -1,26 +0,0 @@

-{{ define "discover" }} -{{ template "head" . }} -{{ template "nav" . }} -<h3>discover cool feedz</h3> -<p><a href="https://sequentialread.com">SequentialRead</a> - home computing, depth, green -https://sequentialread.com/rss/ - -<a href="https://www.themarginalian.org/">themarginalian</a> - philosophy, existentialism, poetry -https://feeds.feedburner.com/brainpickings/rss - -<a href="https://computer.rip">computer.rip</a> - computer history, security -https://computer.rip/rss.xml - -<a href="https://leahreich.substack.com/">Meets Most</a> - soul, authenticity, tech commentary -https://leahreich.substack.com/feed - -<a href="https://kangminsuk.com/">Kang</a> - korean culture, restaurant management, books -https://kangminsuk.com/blog/index.xml - -want ur feed here? write me at j<code>3s<code>@<code>c3f<code>.net - -hand curated by <a href="https://j3s.sh">jes</a> <3 - -</p> -{{ template "tail" . }} -{{ end }}
A files/finger.tmpl.html

@@ -0,0 +1,16 @@

+{{ define "finger" }} +{{ template "head" . }} +{{ template "nav" . }} +<form action="/finger" method="POST"> + <p>finger a website, see what feeds come out, no need to view source!!</p> + <p>example urls:</p> + <ul> + <li>https://j3s.sh</li> + <li>https://www.youtube.com/@RickAstleyYT</li> + </ul> + <label for="urlBox">url: </label> + <input type="text" name="url" id="urlBox" size="50"> + <button type="submit">poke</button> +</form> +{{ template "tail" . }} +{{ end }}
M files/index.tmpl.htmlfiles/index.tmpl.html

@@ -22,6 +22,7 @@ 2025-02

- archive.is -> archive.org - make save async +- replace "discover" with "finger" 2024-04-29
M files/nav.tmpl.htmlfiles/nav.tmpl.html

@@ -8,11 +8,11 @@ </h2>

{{ if .LoggedIn }} <a {{ if eq .Title "user" }}style="font-weight: bold;"{{ end }} href="/{{ .Username }}">home</a> | <a {{ if eq .Title "saves" }}style="font-weight: bold;"{{ end }} href="/saves">saves</a> - | <a {{ if eq .Title "discover" }}style="font-weight: bold;"{{ end }} href="/discover">discover</a> + | <a {{ if eq .Title "finger" }}style="font-weight: bold;"{{ end }} href="/finger">finger</a> | <a {{ if eq .Title "settings" }}style="font-weight: bold;"{{ end }} href="/settings">settings</a> | <a href="/logout">logout</a> {{ else }} - <a {{ if eq .Title "discover" }}style="font-weight: bold;"{{ end }} href="/discover">discover</a> + <a {{ if eq .Title "finger" }}style="font-weight: bold;"{{ end }} href="/finger">finger</a> | <a {{ if eq .Title "login" }}style="font-weight: bold;"{{ end }}href="/login">login</a> {{ end }} </nav>
M go.modgo.mod

@@ -6,6 +6,7 @@ require (

github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 github.com/glebarez/go-sqlite v1.21.2 golang.org/x/crypto v0.19.0 + golang.org/x/net v0.10.0 ) require (
M go.sumgo.sum

@@ -14,6 +14,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=

github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 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/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sys v0.6.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=
M main.gomain.go

@@ -12,7 +12,8 @@ http.HandleFunc("GET /{$}", s.indexHandler)

http.HandleFunc("GET /{username}", s.userHandler) http.HandleFunc("GET /saves", s.userSavesHandler) http.HandleFunc("GET /static/{file}", s.staticHandler) - http.HandleFunc("GET /discover", s.discoverHandler) + http.HandleFunc("GET /finger", s.fingerHandler) + http.HandleFunc("POST /finger", s.fingerHandler) http.HandleFunc("GET /settings", s.settingsHandler) http.HandleFunc("POST /settings/submit", s.settingsSubmitHandler) http.HandleFunc("GET /login", s.loginHandler)
M site.gosite.go

@@ -20,6 +20,7 @@ "git.j3s.sh/vore/rss"

"git.j3s.sh/vore/sqlite" "git.j3s.sh/vore/wayback" "golang.org/x/crypto/bcrypt" + "golang.org/x/net/html" ) type Site struct {

@@ -283,6 +284,107 @@ FetchFailure: fetchErr,

} s.renderPage(w, r, "feedDetails", feedData) +} + +func (s *Site) fingerHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + s.renderPage(w, r, "finger", nil) + } + if r.Method == "POST" { + targetURL := r.FormValue("url") + if targetURL == "" { + http.Error(w, "Please provide a URL.", http.StatusBadRequest) + return + } + + parsed, err := url.ParseRequestURI(targetURL) + if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") { + http.Error(w, "Invalid URL (only http/https allowed).", http.StatusBadRequest) + return + } + + ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil) + if err != nil { + http.Error(w, "failed to build request: "+err.Error(), http.StatusInternalServerError) + return + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + http.Error(w, "failed to fetch URL: "+err.Error(), http.StatusBadGateway) + return + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + http.Error(w, "non-2xx status from site: "+resp.Status, http.StatusBadGateway) + return + } + + doc, err := html.Parse(resp.Body) + if err != nil { + http.Error(w, "failed to parse HTML: "+err.Error(), http.StatusInternalServerError) + return + } + + feeds := discoverFeeds(doc, parsed) + + // Display the results + fmt.Fprintf(w, `<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>%s feeds</title> +</head> +<body style="font-family:sans-serif;">`, html.EscapeString(targetURL)) + + if len(feeds) == 0 { + fmt.Fprintln(w, `<p><em>No RSS/Atom feeds found</em></p>`) + } else { + fmt.Fprintln(w, `<ul>`) + for _, f := range feeds { + fmt.Fprintf(w, `<li>%s</li>`, html.EscapeString(f)) + } + fmt.Fprintln(w, `</ul>`) + } + fmt.Fprintln(w, `</body></html>`) + } +} + +func discoverFeeds(doc *html.Node, base *url.URL) []string { + var feeds []string + var f func(*html.Node) + f = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "link" { + var rel, typ, href string + for _, attr := range n.Attr { + switch attr.Key { + case "rel": + rel = attr.Val + case "type": + typ = attr.Val + case "href": + href = attr.Val + } + } + + if rel == "alternate" && (typ == "application/rss+xml" || typ == "application/atom+xml") { + // make href absolute if necessary + u, err := base.Parse(href) + if err == nil { + feeds = append(feeds, u.String()) + } + } + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(doc) + return feeds } // username fetches a client's username based