beebo
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

add "finger" feature, kill "discover"

j3s.sh 0a75b82e ef317eaf

verified
+126 -29
-26
files/discover.tmpl.html
··· 1 - {{ define "discover" }} 2 - {{ template "head" . }} 3 - {{ template "nav" . }} 4 - <h3>discover cool feedz</h3> 5 - <p><a href="https://sequentialread.com">SequentialRead</a> - home computing, depth, green 6 - https://sequentialread.com/rss/ 7 - 8 - <a href="https://www.themarginalian.org/">themarginalian</a> - philosophy, existentialism, poetry 9 - https://feeds.feedburner.com/brainpickings/rss 10 - 11 - <a href="https://computer.rip">computer.rip</a> - computer history, security 12 - https://computer.rip/rss.xml 13 - 14 - <a href="https://leahreich.substack.com/">Meets Most</a> - soul, authenticity, tech commentary 15 - https://leahreich.substack.com/feed 16 - 17 - <a href="https://kangminsuk.com/">Kang</a> - korean culture, restaurant management, books 18 - https://kangminsuk.com/blog/index.xml 19 - 20 - want ur feed here? write me at j<code>3s<code>@<code>c3f<code>.net 21 - 22 - hand curated by <a href="https://j3s.sh">jes</a> <3 23 - 24 - </p> 25 - {{ template "tail" . }} 26 - {{ end }}
+16
files/finger.tmpl.html
··· 1 + {{ define "finger" }} 2 + {{ template "head" . }} 3 + {{ template "nav" . }} 4 + <form action="/finger" method="POST"> 5 + <p>finger a website, see what feeds come out, no need to view source!!</p> 6 + <p>example urls:</p> 7 + <ul> 8 + <li>https://j3s.sh</li> 9 + <li>https://www.youtube.com/@RickAstleyYT</li> 10 + </ul> 11 + <label for="urlBox">url: </label> 12 + <input type="text" name="url" id="urlBox" size="50"> 13 + <button type="submit">poke</button> 14 + </form> 15 + {{ template "tail" . }} 16 + {{ end }}
+1
files/index.tmpl.html
··· 22 22 23 23 - archive.is -> archive.org 24 24 - make save async 25 + - replace "discover" with "finger" 25 26 26 27 2024-04-29 27 28
+2 -2
files/nav.tmpl.html
··· 8 8 {{ if .LoggedIn }} 9 9 <a {{ if eq .Title "user" }}style="font-weight: bold;"{{ end }} href="/{{ .Username }}">home</a> 10 10 | <a {{ if eq .Title "saves" }}style="font-weight: bold;"{{ end }} href="/saves">saves</a> 11 - | <a {{ if eq .Title "discover" }}style="font-weight: bold;"{{ end }} href="/discover">discover</a> 11 + | <a {{ if eq .Title "finger" }}style="font-weight: bold;"{{ end }} href="/finger">finger</a> 12 12 | <a {{ if eq .Title "settings" }}style="font-weight: bold;"{{ end }} href="/settings">settings</a> 13 13 | <a href="/logout">logout</a> 14 14 {{ else }} 15 - <a {{ if eq .Title "discover" }}style="font-weight: bold;"{{ end }} href="/discover">discover</a> 15 + <a {{ if eq .Title "finger" }}style="font-weight: bold;"{{ end }} href="/finger">finger</a> 16 16 | <a {{ if eq .Title "login" }}style="font-weight: bold;"{{ end }}href="/login">login</a> 17 17 {{ end }} 18 18 </nav>
+1
go.mod
··· 6 6 github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 7 7 github.com/glebarez/go-sqlite v1.21.2 8 8 golang.org/x/crypto v0.19.0 9 + golang.org/x/net v0.10.0 9 10 ) 10 11 11 12 require (
+2
go.sum
··· 14 14 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 15 15 golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= 16 16 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 17 + golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 18 + golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 17 19 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 18 20 golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= 19 21 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+2 -1
main.go
··· 12 12 http.HandleFunc("GET /{username}", s.userHandler) 13 13 http.HandleFunc("GET /saves", s.userSavesHandler) 14 14 http.HandleFunc("GET /static/{file}", s.staticHandler) 15 - http.HandleFunc("GET /discover", s.discoverHandler) 15 + http.HandleFunc("GET /finger", s.fingerHandler) 16 + http.HandleFunc("POST /finger", s.fingerHandler) 16 17 http.HandleFunc("GET /settings", s.settingsHandler) 17 18 http.HandleFunc("POST /settings/submit", s.settingsSubmitHandler) 18 19 http.HandleFunc("GET /login", s.loginHandler)
+102
site.go
··· 20 20 "git.j3s.sh/vore/sqlite" 21 21 "git.j3s.sh/vore/wayback" 22 22 "golang.org/x/crypto/bcrypt" 23 + "golang.org/x/net/html" 23 24 ) 24 25 25 26 type Site struct { ··· 283 284 } 284 285 285 286 s.renderPage(w, r, "feedDetails", feedData) 287 + } 288 + 289 + func (s *Site) fingerHandler(w http.ResponseWriter, r *http.Request) { 290 + if r.Method == "GET" { 291 + s.renderPage(w, r, "finger", nil) 292 + } 293 + if r.Method == "POST" { 294 + targetURL := r.FormValue("url") 295 + if targetURL == "" { 296 + http.Error(w, "Please provide a URL.", http.StatusBadRequest) 297 + return 298 + } 299 + 300 + parsed, err := url.ParseRequestURI(targetURL) 301 + if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") { 302 + http.Error(w, "Invalid URL (only http/https allowed).", http.StatusBadRequest) 303 + return 304 + } 305 + 306 + ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) 307 + defer cancel() 308 + 309 + req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil) 310 + if err != nil { 311 + http.Error(w, "failed to build request: "+err.Error(), http.StatusInternalServerError) 312 + return 313 + } 314 + 315 + resp, err := http.DefaultClient.Do(req) 316 + if err != nil { 317 + http.Error(w, "failed to fetch URL: "+err.Error(), http.StatusBadGateway) 318 + return 319 + } 320 + defer resp.Body.Close() 321 + 322 + if resp.StatusCode < 200 || resp.StatusCode > 299 { 323 + http.Error(w, "non-2xx status from site: "+resp.Status, http.StatusBadGateway) 324 + return 325 + } 326 + 327 + doc, err := html.Parse(resp.Body) 328 + if err != nil { 329 + http.Error(w, "failed to parse HTML: "+err.Error(), http.StatusInternalServerError) 330 + return 331 + } 332 + 333 + feeds := discoverFeeds(doc, parsed) 334 + 335 + // Display the results 336 + fmt.Fprintf(w, `<!DOCTYPE html> 337 + <html> 338 + <head> 339 + <meta charset="utf-8"> 340 + <title>%s feeds</title> 341 + </head> 342 + <body style="font-family:sans-serif;">`, html.EscapeString(targetURL)) 343 + 344 + if len(feeds) == 0 { 345 + fmt.Fprintln(w, `<p><em>No RSS/Atom feeds found</em></p>`) 346 + } else { 347 + fmt.Fprintln(w, `<ul>`) 348 + for _, f := range feeds { 349 + fmt.Fprintf(w, `<li>%s</li>`, html.EscapeString(f)) 350 + } 351 + fmt.Fprintln(w, `</ul>`) 352 + } 353 + fmt.Fprintln(w, `</body></html>`) 354 + } 355 + } 356 + 357 + func discoverFeeds(doc *html.Node, base *url.URL) []string { 358 + var feeds []string 359 + var f func(*html.Node) 360 + f = func(n *html.Node) { 361 + if n.Type == html.ElementNode && n.Data == "link" { 362 + var rel, typ, href string 363 + for _, attr := range n.Attr { 364 + switch attr.Key { 365 + case "rel": 366 + rel = attr.Val 367 + case "type": 368 + typ = attr.Val 369 + case "href": 370 + href = attr.Val 371 + } 372 + } 373 + 374 + if rel == "alternate" && (typ == "application/rss+xml" || typ == "application/atom+xml") { 375 + // make href absolute if necessary 376 + u, err := base.Parse(href) 377 + if err == nil { 378 + feeds = append(feeds, u.String()) 379 + } 380 + } 381 + } 382 + for c := n.FirstChild; c != nil; c = c.NextSibling { 383 + f(c) 384 + } 385 + } 386 + f(doc) 387 + return feeds 286 388 } 287 389 288 390 // username fetches a client's username based