add "finger" feature, kill "discover"
Jes Olson j3s@c3f.net
Tue, 25 Feb 2025 01:11:50 -0500
8 files changed,
126 insertions(+),
29 deletions(-)
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.html
→
files/index.tmpl.html
@@ -22,6 +22,7 @@ 2025-02
- archive.is -> archive.org - make save async +- replace "discover" with "finger" 2024-04-29
M
go.sum
→
go.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.go
→
main.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.go
→
site.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