From ee4dfdad185b2263c3f7cc0ce42e9135f06657c0 Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Sun, 28 Sep 2025 12:42:59 +0200 Subject: [PATCH] add page for microblog header content --- dev.sh | 4 +- internal/microblog/microblog.go | 146 ++++++++++++++++++++------------ internal/pocketbase/pb.go | 1 + main.go | 4 +- pages/microblog.gmi | 12 +++ 5 files changed, 110 insertions(+), 57 deletions(-) create mode 100644 pages/microblog.gmi diff --git a/dev.sh b/dev.sh index 23a241e..a36cc39 100755 --- a/dev.sh +++ b/dev.sh @@ -2,4 +2,6 @@ set -e -go run main.go -cert=./keys/localhost.crt.pem -key=./keys/localhost_key.pem -host=localhost:8080 +source .env + +fd | entr -r go run main.go -cert=./keys/localhost.crt.pem -key=./keys/localhost_key.pem -host=localhost:8080 diff --git a/internal/microblog/microblog.go b/internal/microblog/microblog.go index 09a6ee9..ff36dc3 100644 --- a/internal/microblog/microblog.go +++ b/internal/microblog/microblog.go @@ -5,6 +5,8 @@ import ( "fmt" "gemini_site/internal/pocketbase" "log/slog" + "os" + "strings" "time" gemini "github.com/kulak/gemini" @@ -20,14 +22,6 @@ const ( sourceNostr source = "nostr" ) -var supportedSources = map[source]bool{ - sourceNostr: true, - sourcePleroma: false, - sourceBlueSky: false, - sourceMastodon: false, - sourcePixelfed: false, -} - // Post represents a single blog post type post struct { ID string @@ -36,6 +30,7 @@ type post struct { // TODO: add support for images, must extend the pocketbase query // Images []string Timestamp time.Time + Source source } // PBPost represents a microblog post from PocketBase @@ -108,14 +103,32 @@ func NewMicroBlog(pbClient *pocketbase.PocketBaseClient) *MicroBlog { // } // GetPost retrieves a post by ID -// func (mb *MicroBlog) GetPost(id string) (*Post, bool) { -// for _, post := range mb.posts { -// if post.ID == id { -// return &post, true -// } -// } -// return nil, false -// } +// +// func (mb *MicroBlog) GetPost(id string) (*Post, bool) { +// for _, post := range mb.posts { +// if post.ID == id { +// return &post, true +// } +// } +// return nil, false +// } +func transformNostrPost(p pbPost) post { + var nostrPost nostrPost + if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil { + slog.Error("Problem unmarshalling nostr post", "error", err) + return post{} + } + content := nostrPost.Content + content = strings.ReplaceAll(content, "#", "\\#") + // content = strings.ReplaceAll(content, "\t", "\\t") + return post{ + ID: p.ID, + RemoteID: p.RemoteID, + Content: content, + Timestamp: time.Now(), + Source: source(p.Source), + } +} // GetRecentPosts returns the most recent posts func (mb *MicroBlog) GetRecentPosts(limit int) ([]post, error) { @@ -133,17 +146,19 @@ func (mb *MicroBlog) GetRecentPosts(limit int) ([]post, error) { var filteredPosts []post for _, p := range rawPosts { if p.Source == SourceNostr { - var nostrPost nostrPost - if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil { - slog.Error("Problem unmarshalling nostr post", "error", err) - continue - } - filteredPosts = append(filteredPosts, post{ - ID: p.ID, - RemoteID: p.RemoteID, - Content: nostrPost.Content, - Timestamp: time.Now(), - }) + filteredPosts = append(filteredPosts, transformNostrPost(p)) + + // var nostrPost nostrPost + // if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil { + // slog.Error("Problem unmarshalling nostr post", "error", err) + // continue + // } + // filteredPosts = append(filteredPosts, post{ + // ID: p.ID, + // RemoteID: p.RemoteID, + // Content: nostrPost.Content, + // Timestamp: time.Now(), + // }) continue } } @@ -152,47 +167,68 @@ func (mb *MicroBlog) GetRecentPosts(limit int) ([]post, error) { // HandleBlogRequest handles Gemini requests for the microblog func (mb *MicroBlog) HandleBlogRequest(w gemini.ResponseWriter, req *gemini.Request) { - // path := req.URL.Path + path := req.URL.Path - // switch { - // case path == "/microblog" || path == "/microblog/": - // mb.serveBlogIndex(w, req) + switch { + case path == "/microblog" || path == "/microblog/": + mb.serveIndex(w, req) // case strings.HasPrefix(path, "/microblog/post/"): // postID := strings.TrimPrefix(path, "/microblog/post/") // mb.servePost(w, req, postID) - // default: - // w.WriteStatusMsg(gemini.StatusNotFound, "Blog page not found") - // } + default: + w.WriteStatusMsg(gemini.StatusNotFound, "Page not found") + } +} + +func drawPost(builder *strings.Builder, p post) { + builder.WriteString("+------------------------------------------+\n") + builder.WriteString(p.Content) + builder.WriteString("\n") + builder.WriteString(fmt.Sprintf("source: %s, id: %s...\n", p.Source, p.RemoteID[:10])) + builder.WriteString("+------------------------------------------+\n\n\n") + + // builder.WriteString(fmt.Sprintf("=> /blog/post/%s %s\n", p.ID, p.Title)) + // builder.WriteString(fmt.Sprintf(" By %s on %s\n\n", + // p.Timestamp.Format("2006-01-02 15:04"))) } // serveBlogIndex serves the main blog page with recent posts -// func (mb *MicroBlog) serveBlogIndex(w gemini.ResponseWriter, req *gemini.Request) { -// w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") +func (mb *MicroBlog) serveIndex(w gemini.ResponseWriter, req *gemini.Request) { + w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") -// var content strings.Builder -// content.WriteString("# Gemini Microblog\n\n") + var content strings.Builder + // content.WriteString("# Gemini Microblog\n\n") + // content.WriteString("Here are my microblog posts from various plantforms\n") + // Read and include the contents of ../../pages/microblog.gmi + page, err := os.ReadFile("./pages/microblog.gmi") + if err != nil { + slog.Error("Problem reading microblog page", "error", err) + return + } + content.Write(page) + content.WriteString("\n") -// posts := mb.GetRecentPosts(10) + posts, err := mb.GetRecentPosts(20) + if err != nil { + content.WriteString("Error fetching posts: " + err.Error() + "\n\n") + } -// if len(posts) == 0 { -// content.WriteString("No posts yet. Be the first to write something!\n\n") -// } else { -// content.WriteString("## Recent Posts\n\n") + if len(posts) == 0 { + content.WriteString("No posts found.\n\n") + } else { + content.WriteString("## Recent Posts\n\n") -// for _, post := range posts { -// content.WriteString(fmt.Sprintf("=> /blog/post/%s %s\n", post.ID, post.Title)) -// content.WriteString(fmt.Sprintf(" By %s on %s\n\n", -// post.Author, -// post.Timestamp.Format("2006-01-02 15:04"))) -// } -// } + for _, post := range posts { + drawPost(&content, post) + } + } -// content.WriteString("## Actions\n\n") -// content.WriteString("=> /blog/new Write a new post\n") -// content.WriteString("=> / Back to home\n") + content.WriteString("## Nav\n\n") + // content.WriteString("=> /blog/new Write a new post\n") + content.WriteString("=> / Back to home\n") -// w.WriteBody([]byte(content.String())) -// } + w.WriteBody([]byte(content.String())) +} // servePost serves a single blog post // func (mb *MicroBlog) servePost(w gemini.ResponseWriter, req *gemini.Request, postID string) { diff --git a/internal/pocketbase/pb.go b/internal/pocketbase/pb.go index c0e1685..e7bfdf8 100644 --- a/internal/pocketbase/pb.go +++ b/internal/pocketbase/pb.go @@ -176,6 +176,7 @@ func (c *PocketBaseClient) GetList( "perPage": {strconv.Itoa(pageSize)}, "sort": {sort}, "skipTotal": {"true"}, + "filter": {"source = \"nostr\""}, // TODO: add additional fields like image and tag? } apiURL := fmt.Sprintf("%s/api/collections/%s/records", c.host, collection) diff --git a/main.go b/main.go index f73ddea..4a93954 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "time" "gemini_site/internal/microblog" + "gemini_site/internal/pocketbase" gemini "github.com/kulak/gemini" ) @@ -102,8 +103,9 @@ func main() { flag.StringVar(&key, "key", "server.key.pem", "private key associated with certificate file") flag.Parse() + pbClient := pocketbase.NewPocketBaseClient() handler := MainHandler{ - blog: microblog.NewHandler(), + blog: microblog.NewHandler(pbClient), } err := gemini.ListenAndServe(host, cert, key, gemini.TrapPanic(handler.ServeGemini)) diff --git a/pages/microblog.gmi b/pages/microblog.gmi new file mode 100644 index 0000000..45278e0 --- /dev/null +++ b/pages/microblog.gmi @@ -0,0 +1,12 @@ +# Microblog + +An aggregation of tweet like posts from my various social media platforms. + +clearnet version: + +=> https://travisshears.com/micro-blog + +So for it renders posts from: +* nostr, id: nprofile1qyxhwumn8ghj7mn0wvhxcmmvqqs9udcv9uhqggjz87js9rtaph4lajlxnxsvwvm7zwdjt6etzyk52rgeg4wrz +* mastodon, id: dice.camp/@travisshears +* bluesky, id: @travisshears.bsky.social