all repos — flounder @ 4791b3472756756ba175b7d8dc33e842d062c346

A small site builder for the Gemini protocol

Remove feeds page, replace with following page

Add sensible limits to feed following code
alex wennerberg alex@alexwennerberg.com
Thu, 28 Jan 2021 21:33:31 -0800
commit

4791b3472756756ba175b7d8dc33e842d062c346

parent

bb9c710d0ee19b314dc8806a153e99003196859e

M feed.gofeed.go

@@ -3,8 +3,11 @@

import ( "bufio" "fmt" + "git.sr.ht/~adnano/go-gemini" "github.com/mmcdole/gofeed" "log" + "net/http" + "net/url" "os" "path" "sort"

@@ -19,7 +22,6 @@

func feedsWorker() { log.Println("Starting feeds worker") for { - time.Sleep(time.Hour * 1) users, err := getActiveUserNames() if err != nil { // Handle error somehow

@@ -29,40 +31,69 @@ }

for _, user := range users { writeAllFeeds(user) } + time.Sleep(time.Hour * 1) } } func writeAllFeeds(user string) error { // Open file file, err := os.Open(path.Join(getUserDirectory(user), followingPath)) - if err != nil { - if os.IsNotExist(err) { - // TODO - return nil - } - return err - } log.Println("Writing feeds for user " + user) defer file.Close() feedData := []*gofeed.Feed{} - scanner := bufio.NewScanner(file) - for scanner.Scan() { - feedURL := scanner.Text() - // TODO if scheme is gemini and filetype is gemini... gemtext - // TODO if scheme is gemini and filetype is xml/rss... fetch data and parse - // TODO rate limit etc - fp := gofeed.NewParser() - feed, err := fp.ParseURL(feedURL) - if err != nil { - log.Println("Error getting feed " + feedURL) - } else { + if err == nil { + scanner := bufio.NewScanner(file) + count := 1 + for scanner.Scan() { + if count > 100 { // max number of lines + break + } + count = count + 1 + feedURL := scanner.Text() + parsed, err := url.Parse(feedURL) + var feed *gofeed.Feed + fp := gofeed.NewParser() + if err != nil { + log.Println("Invalid url " + feedURL) + } + if parsed.Scheme == "gemini" { + client := gemini.Client{ + Timeout: 10 * time.Second, + } + res, err := client.Get(feedURL) + defer res.Body.Close() + if err != nil { + log.Println(err) + continue + } + if err != nil { + log.Println(err) + continue + } + feed, err = fp.Parse(res.Body) + if err != nil { + log.Println(err) + continue + } + } else { + // TODO if scheme is gemini and filetype is gemini... gemtext + // TODO rate limit etc + fp.Client = &http.Client{ + Timeout: 10 * time.Second, + } + feed, err = fp.ParseURL(feedURL) + if err != nil { + log.Println("Error getting feed " + feedURL) + continue + } + } log.Println("Got feed data from " + feedURL) feedData = append(feedData, feed) } - } - if err := scanner.Err(); err != nil { - return err + if err := scanner.Err(); err != nil { + return err + } } // Aggregate and sort by date type feedPlusItem struct {

@@ -89,6 +120,10 @@ }

sort.Slice(data.FeedItems, func(i, j int) bool { return data.FeedItems[i].FeedItem.UpdatedParsed.After(*data.FeedItems[j].FeedItem.UpdatedParsed) }) + maxItems := 100 + if len(data.FeedItems) > maxItems { + data.FeedItems = data.FeedItems[:maxItems] + } outputf, err := os.OpenFile(path.Join(getUserDirectory(user), followingFile), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) if err != nil {
M gemfeed.gogemfeed.go

@@ -123,35 +123,6 @@ })

return &feed } -// TODO definitely cache this function -// TODO include generateFeedFromFolder for "gemfeed" folders -func getAllGemfeedEntries() ([]FeedEntry, []Gemfeed, error) { - maxItems := 50 - var feedEntries []FeedEntry - var feeds []Gemfeed - users, err := getActiveUserNames() - if err != nil { - return nil, nil, err - } else { - for _, user := range users { - fe := generateFeedFromUser(user) - if len(fe.Entries) > 0 { - feeds = append(feeds, *fe.Entries[0].Feed) - for _, e := range fe.Entries { - feedEntries = append(feedEntries, e) - } - } - } - } - sort.Slice(feedEntries, func(i, j int) bool { - return feedEntries[i].Date.After(feedEntries[j].Date) - }) - if len(feedEntries) > maxItems { - return feedEntries[:maxItems], feeds, nil - } - return feedEntries, feeds, nil -} - var GemfeedRegex = regexp.MustCompile(`=>\s*(\S+)\s([0-9]{4}-[0-9]{2}-[0-9]{2})\s?-?\s?(.*)`) // Parsed Gemfeed text Returns error if not a gemfeed
M http.gohttp.go

@@ -55,7 +55,7 @@ http.ServeFile(w, r, fileName) // TODO better error handling

return } - user := newGetAuthUser(r) + user := getAuthUser(r) indexFiles, err := getIndexFiles(user.IsAdmin) if err != nil { panic(err)

@@ -76,26 +76,8 @@ panic(err)

} } -func feedHandler(w http.ResponseWriter, r *http.Request) { - user := newGetAuthUser(r) - feedEntries, feeds, err := getAllGemfeedEntries() - if err != nil { - panic(err) - } - data := struct { - Config Config - FeedEntries []FeedEntry - Feeds []Gemfeed - AuthUser AuthUser - }{c, feedEntries, feeds, user} - err = t.ExecuteTemplate(w, "feed.html", data) - if err != nil { - panic(err) - } -} - func editFileHandler(w http.ResponseWriter, r *http.Request) { - user := newGetAuthUser(r) + user := getAuthUser(r) if !user.LoggedIn { renderDefaultError(w, http.StatusForbidden) return

@@ -196,7 +178,7 @@ }

func uploadFilesHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { - user := newGetAuthUser(r) + user := getAuthUser(r) if !user.LoggedIn { renderDefaultError(w, http.StatusForbidden) return

@@ -241,7 +223,7 @@ IsAdmin bool

ImpersonatingUser string // used if impersonating } -func newGetAuthUser(r *http.Request) AuthUser { +func getAuthUser(r *http.Request) AuthUser { session, _ := SessionStore.Get(r, "cookie-session") user, ok := session.Values["auth_user"].(string) impers, _ := session.Values["impersonating_user"].(string)

@@ -255,7 +237,7 @@ }

} func mySiteHandler(w http.ResponseWriter, r *http.Request) { - user := newGetAuthUser(r) + user := getAuthUser(r) if !user.LoggedIn { renderDefaultError(w, http.StatusForbidden) return

@@ -274,7 +256,7 @@ _ = t.ExecuteTemplate(w, "my_site.html", data)

} func myAccountHandler(w http.ResponseWriter, r *http.Request) { - user := newGetAuthUser(r) + user := getAuthUser(r) authUser := user.Username if !user.LoggedIn { renderDefaultError(w, http.StatusForbidden)

@@ -334,7 +316,7 @@ session.Save(r, w)

} } // reset auth - user = newGetAuthUser(r) + user = getAuthUser(r) data.Errors = errors data.AuthUser = user data.MyUser.Email = newEmail

@@ -344,7 +326,7 @@ }

} func archiveHandler(w http.ResponseWriter, r *http.Request) { - authUser := newGetAuthUser(r) + authUser := getAuthUser(r) if !authUser.LoggedIn { renderDefaultError(w, http.StatusForbidden) return

@@ -492,7 +474,7 @@ }

} func deleteFileHandler(w http.ResponseWriter, r *http.Request) { - user := newGetAuthUser(r) + user := getAuthUser(r) if !user.LoggedIn { renderDefaultError(w, http.StatusForbidden) return

@@ -505,7 +487,7 @@ http.Redirect(w, r, "/my_site", http.StatusSeeOther)

} func adminHandler(w http.ResponseWriter, r *http.Request) { - user := newGetAuthUser(r) + user := getAuthUser(r) if !user.IsAdmin { renderDefaultError(w, http.StatusForbidden) return

@@ -634,7 +616,7 @@ }

} func deleteAccountHandler(w http.ResponseWriter, r *http.Request) { - user := newGetAuthUser(r) + user := getAuthUser(r) if r.Method == "POST" { r.ParseForm() validate := r.Form.Get("validate-delete")

@@ -653,7 +635,7 @@ }

} func resetPasswordHandler(w http.ResponseWriter, r *http.Request) { - user := newGetAuthUser(r) + user := getAuthUser(r) data := struct { Config Config AuthUser AuthUser

@@ -699,7 +681,7 @@ }

} func adminUserHandler(w http.ResponseWriter, r *http.Request) { - user := newGetAuthUser(r) + user := getAuthUser(r) if r.Method == "POST" { if !user.IsAdmin { renderDefaultError(w, http.StatusForbidden)

@@ -790,7 +772,6 @@ hostname := s[0]

port := c.HttpPort serveMux.HandleFunc(hostname+"/", rootHandler) - serveMux.HandleFunc(hostname+"/feed", feedHandler) serveMux.HandleFunc(hostname+"/my_site", mySiteHandler) serveMux.HandleFunc(hostname+"/me", myAccountHandler) serveMux.HandleFunc(hostname+"/my_site/flounder-archive.zip", archiveHandler)
M log.golog.go

@@ -35,7 +35,7 @@ // buildCommonLogLine builds a log entry for req in Apache Common Log Format.

// ts is the timestamp with which the entry should be logged. // status and size are used to provide the response HTTP status and size. func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte { - user := newGetAuthUser(req) + user := getAuthUser(req) username := "-" if user.Username != "" { username = user.Username
M templates/edit_file.htmltemplates/edit_file.html

@@ -4,6 +4,9 @@ <form id="edit-form" action="/edit/{{.FileName}}" method="POST">

<label for="rename">Rename:</label> <input type="text" value="{{.FileName}}" id="rename" name="rename"> {{ if .IsText }} + {{ if eq .FileName "following.txt" }} + <p><em>Add URLs here of feeds you would like to follow. They will be available at <a href="//{{.AuthUser.Username}}.{{.Host}}/following.gmi">following.gmi</a>. For more information, see <a href="https://admin.flounder.online/following-pages.gmi">Following Feeds</a></em></p> + {{ end}} {{ if .IsGemini }} <p> <em>For help with the Gemini markup format, see the <a href="https://admin.flounder.online/gemini_text_guide.gmi">Gemtext Guide</a></em>
M templates/following.gmitemplates/following.gmi

@@ -1,7 +1,9 @@

# {{.User}}'s Following A collection of feeds that {{.User}} is following -=> following.txt All feeds +=> following.txt {{ range .FeedItems }} => {{.FeedItem.Link}} {{.Date}} {{.Feed.Title}} -- {{.FeedItem.Title}}{{ end }} +For more information about setting up this page, see: +=> //admin.flounder.online/following-feeds.gmi
M templates/my_site.htmltemplates/my_site.html

@@ -12,6 +12,7 @@ <br>

<br> <h3>Your files:</h3> {{ define "file" }} +{{ if ne .Name "following.gmi" }} <tr> <div> {{ if gt (len .Children) 0 }}

@@ -52,6 +53,7 @@ {{ end }}

</td> </div> </tr> +{{ end }} {{ end }} <table> {{ range .Files }}
M templates/nav.htmltemplates/nav.html

@@ -1,7 +1,7 @@

<nav> <a href="/">home</a> - <a href="/feed">feed</a> {{ if .AuthUser.LoggedIn }} + <a href="//{{.AuthUser.Username}}.{{.Config.Host}}/following.gmi">following</a> <a href="/my_site">my_site</a> <a href="/me">me</a> {{ if .AuthUser.IsAdmin }}