diff --git a/internal/microblog/microblog.gmi b/internal/microblog/microblog.gmi
index 691f0d3..33b47e2 100644
--- a/internal/microblog/microblog.gmi
+++ b/internal/microblog/microblog.gmi
@@ -6,9 +6,8 @@ clearnet version:
=> https://travisshears.com/micro-blog
-So for it renders posts from:
-* nostr, id: nprofile1qyxhwumn8ghj7mn0wvhxcmmvqqs9udcv9uhqggjz87js9rtaph4lajlxnxsvwvm7zwdjt6etzyk52rgeg4wrz
+Here post from nostr, mastodon, and bluesky are rendered.
-I plan to add more platforms in the future:
-* mastodon, id: dice.camp/@travisshears
-* bluesky, id: @travisshears.bsky.social
+My nostr id is: nprofile1qyxhwumn8ghj7mn0wvhxcmmvqqs9udcv9uhqggjz87js9rtaph4lajlxnxsvwvm7zwdjt6etzyk52rgeg4wrz I run my own relay wss://nostr.travisshears.com but you can also find me on the most popular relays.
+=> https://bsky.app/profile/travisshears.bsky.social @travisshears on Bluesky
+=> https://dice.camp/@travisshears @travisshears on Mastodon via dice.camp
diff --git a/internal/microblog/microblog.go b/internal/microblog/microblog.go
index 02a8147..b8e442e 100644
--- a/internal/microblog/microblog.go
+++ b/internal/microblog/microblog.go
@@ -61,59 +61,34 @@ func NewMicroBlog(pbClient *pocketbase.PocketBaseClient) *MicroBlog {
return mb
}
-// // Add some sample posts
-// mb.addSamplePosts()
+func escapeHashTags(content string) string {
+ // TODO: maybe prettier way to do this with a code
+ return strings.ReplaceAll(content, "#", "\\#")
+}
-// return mb
-// }
+func removeHTMLTags(content string) string {
+ // Strip simple HTML tags. Paragraphs become newlines.
+ // Replace opening
tags with nothing, closing
with newline.
+ rePStart := regexp.MustCompile(`(?i)]*>`)
+ rePEnd := regexp.MustCompile(`(?i)
`)
+ content = rePStart.ReplaceAllString(content, "")
+ content = rePEnd.ReplaceAllString(content, "\n")
-// addSamplePosts adds some initial content
-// func (mb *MicroBlog) addSamplePosts() {
-// samplePosts := []Post{
-// {
-// ID: "1",
-// Title: "Welcome to the Gemini Microblog",
-// Content: "This is the first post on our Gemini-powered microblog! It's simple, fast, and distraction-free.",
-// Author: "Admin",
-// Timestamp: time.Now().Add(-2 * time.Hour),
-// },
-// {
-// ID: "2",
-// Title: "The Beauty of Simplicity",
-// Content: "Gemini protocol encourages us to focus on content over presentation. This microblog embodies that philosophy.",
-// Author: "Admin",
-// Timestamp: time.Now().Add(-1 * time.Hour),
-// },
-// }
+ // Convert
to newline
+ reBr := regexp.MustCompile(`(?i)
`)
+ content = reBr.ReplaceAllString(content, "\n")
-// mb.posts = append(mb.posts, samplePosts...)
-// }
+ // Remove any remaining tags
+ reTags := regexp.MustCompile(`(?i)<[^>]+>`)
+ content = reTags.ReplaceAllString(content, "")
-// AddPost adds a new post to the blog
-// func (mb *MicroBlog) AddPost(title, content, author string) string {
-// id := fmt.Sprintf("%d", time.Now().Unix())
-// post := Post{
-// ID: id,
-// Title: title,
-// Content: content,
-// Author: author,
-// Timestamp: time.Now(),
-// }
+ // Normalize whitespace: trim and collapse excessive blank lines
+ content = strings.TrimSpace(content)
+ reMultiNewlines := regexp.MustCompile(`\n{3,}`)
+ return reMultiNewlines.ReplaceAllString(content, "\n\n")
-// mb.posts = append(mb.posts, post)
-// return id
-// }
+}
-// 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 transformNostrPost(p pbPost) post {
var nostrPost nostrPost
if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil {
@@ -121,8 +96,63 @@ func transformNostrPost(p pbPost) post {
return post{}
}
content := nostrPost.Content
- content = strings.ReplaceAll(content, "#", "\\#")
- // content = strings.ReplaceAll(content, "\t", "\\t")
+ content = escapeHashTags(content)
+ return post{
+ ID: p.ID,
+ RemoteID: p.RemoteID,
+ Content: content,
+ Timestamp: time.Now(),
+ Source: source(p.Source),
+ }
+}
+
+func transformBlueSkyPost(p pbPost) post {
+ var fullPost struct {
+ Record struct {
+ Text string `json:"text"`
+ } `json:"record"`
+ }
+ if err := json.Unmarshal(p.FullPost, &fullPost); err != nil {
+ slog.Error("Problem unmarshalling bluesky post", "error", err)
+ return post{}
+ }
+ content := fullPost.Record.Text
+ content = escapeHashTags(content)
+ return post{
+ ID: p.ID,
+ RemoteID: p.RemoteID,
+ Content: content,
+ Timestamp: time.Now(),
+ Source: source(p.Source),
+ }
+}
+
+func transformMastodonPost(p pbPost) post {
+ var fullPost struct {
+ Content string `json:"content"`
+ Reblog *struct {
+ Content string `json:"content"`
+ Account *struct {
+ Acct string `json:"acct"`
+ } `json:"account"`
+ } `json:"reblog"`
+ }
+ var content string
+ if err := json.Unmarshal(p.FullPost, &fullPost); err != nil {
+ slog.Error("Problem unmarshalling mastodon post", "error", err)
+ return post{}
+ }
+ if fullPost.Reblog != nil {
+ content = fmt.Sprintf(
+ "reblogged post from %s:\n%s\n",
+ fullPost.Reblog.Account.Acct,
+ fullPost.Reblog.Content,
+ )
+ } else {
+ content = fullPost.Content
+ }
+ content = escapeHashTags(content)
+ content = removeHTMLTags(content)
return post{
ID: p.ID,
RemoteID: p.RemoteID,
@@ -147,21 +177,13 @@ func (mb *MicroBlog) GetRecentPosts(limit int, page int) ([]post, error) {
var filteredPosts []post
for _, p := range rawPosts {
- if p.Source == SourceNostr {
+ switch p.Source {
+ case SourceNostr:
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
+ case SourceMastodon:
+ filteredPosts = append(filteredPosts, transformMastodonPost(p))
+ case SourceBlueSky:
+ filteredPosts = append(filteredPosts, transformBlueSkyPost(p))
}
}
return filteredPosts, nil
@@ -208,7 +230,7 @@ func (mb *MicroBlog) HandleBlogRequest(w gemini.ResponseWriter, req *gemini.Requ
}
func drawPost(builder *strings.Builder, p post) {
- builder.WriteString(fmt.Sprintf("=> / 🖋️ nostr post: %s \n", p.ID))
+ builder.WriteString(fmt.Sprintf("=> / 🖋️ %s post: %s \n", p.Source, p.ID))
builder.WriteString(formatContent(p.Content))
builder.WriteString(fmt.Sprintf("\nposted: %s\n", p.Timestamp.Format("2006-01-02 15:04")))
builder.WriteString("\n\n")
diff --git a/internal/pocketbase/pb.go b/internal/pocketbase/pb.go
index e7bfdf8..2967112 100644
--- a/internal/pocketbase/pb.go
+++ b/internal/pocketbase/pb.go
@@ -176,7 +176,7 @@ func (c *PocketBaseClient) GetList(
"perPage": {strconv.Itoa(pageSize)},
"sort": {sort},
"skipTotal": {"true"},
- "filter": {"source = \"nostr\""},
+ "filter": {"source = \"nostr\" || source = \"mastodon\" || source = \"blue_sky\""},
// TODO: add additional fields like image and tag?
}
apiURL := fmt.Sprintf("%s/api/collections/%s/records", c.host, collection)