Compare commits

...

3 commits

Author SHA1 Message Date
a88994add4
mark tasks as done 2025-10-16 11:36:50 +02:00
eca8acddff
add mastodon and bluesky to microblog
")
")
{3,}`)

")
%s
",
", p.Source, p.ID))
posted: %s
", p.Timestamp.Format("2006-01-02 15:04")))

")
2025-10-16 11:35:43 +02:00
86f3ea76b3
imporve microblog text layout
")
")
", p.Source, p.RemoteID[:10]))


")
", p.ID))
posted: %s
", p.Timestamp.Format("2006-01-02 15:04")))

")
", p.ID, p.Title))

",

")
")
")
2025-10-16 10:51:53 +02:00
4 changed files with 103 additions and 83 deletions

View file

@ -6,9 +6,8 @@ clearnet version:
=> https://travisshears.com/micro-blog => https://travisshears.com/micro-blog
So for it renders posts from: Here post from nostr, mastodon, and bluesky are rendered.
* nostr, id: nprofile1qyxhwumn8ghj7mn0wvhxcmmvqqs9udcv9uhqggjz87js9rtaph4lajlxnxsvwvm7zwdjt6etzyk52rgeg4wrz
I plan to add more platforms in the future: 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.
* mastodon, id: dice.camp/@travisshears => https://bsky.app/profile/travisshears.bsky.social @travisshears on Bluesky
* bluesky, id: @travisshears.bsky.social => https://dice.camp/@travisshears @travisshears on Mastodon via dice.camp

View file

@ -61,59 +61,34 @@ func NewMicroBlog(pbClient *pocketbase.PocketBaseClient) *MicroBlog {
return mb return mb
} }
// // Add some sample posts func escapeHashTags(content string) string {
// mb.addSamplePosts() // 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 <p> tags with nothing, closing </p> with newline.
rePStart := regexp.MustCompile(`(?i)<p[^>]*>`)
rePEnd := regexp.MustCompile(`(?i)</p>`)
content = rePStart.ReplaceAllString(content, "")
content = rePEnd.ReplaceAllString(content, "\n")
// addSamplePosts adds some initial content // Convert <br> to newline
// func (mb *MicroBlog) addSamplePosts() { reBr := regexp.MustCompile(`(?i)<br\s*/?>`)
// samplePosts := []Post{ content = reBr.ReplaceAllString(content, "\n")
// {
// 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),
// },
// }
// 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 // Normalize whitespace: trim and collapse excessive blank lines
// func (mb *MicroBlog) AddPost(title, content, author string) string { content = strings.TrimSpace(content)
// id := fmt.Sprintf("%d", time.Now().Unix()) reMultiNewlines := regexp.MustCompile(`\n{3,}`)
// post := Post{ return reMultiNewlines.ReplaceAllString(content, "\n\n")
// ID: id,
// Title: title,
// Content: content,
// Author: author,
// Timestamp: time.Now(),
// }
// 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 { func transformNostrPost(p pbPost) post {
var nostrPost nostrPost var nostrPost nostrPost
if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil { if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil {
@ -121,8 +96,63 @@ func transformNostrPost(p pbPost) post {
return post{} return post{}
} }
content := nostrPost.Content content := nostrPost.Content
content = strings.ReplaceAll(content, "#", "\\#") content = escapeHashTags(content)
// content = strings.ReplaceAll(content, "\t", "\\t") 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{ return post{
ID: p.ID, ID: p.ID,
RemoteID: p.RemoteID, RemoteID: p.RemoteID,
@ -147,26 +177,22 @@ func (mb *MicroBlog) GetRecentPosts(limit int, page int) ([]post, error) {
var filteredPosts []post var filteredPosts []post
for _, p := range rawPosts { for _, p := range rawPosts {
if p.Source == SourceNostr { switch p.Source {
case SourceNostr:
filteredPosts = append(filteredPosts, transformNostrPost(p)) filteredPosts = append(filteredPosts, transformNostrPost(p))
case SourceMastodon:
// var nostrPost nostrPost filteredPosts = append(filteredPosts, transformMastodonPost(p))
// if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil { case SourceBlueSky:
// slog.Error("Problem unmarshalling nostr post", "error", err) filteredPosts = append(filteredPosts, transformBlueSkyPost(p))
// continue
// }
// filteredPosts = append(filteredPosts, post{
// ID: p.ID,
// RemoteID: p.RemoteID,
// Content: nostrPost.Content,
// Timestamp: time.Now(),
// })
continue
} }
} }
return filteredPosts, nil return filteredPosts, nil
} }
func formatContent(content string) string {
return replaceLinks(content)
}
func replaceLinks(content string) string { func replaceLinks(content string) string {
// Regex: ^(https?://\S+) // Regex: ^(https?://\S+)
// ^ : start of line // ^ : start of line
@ -204,16 +230,11 @@ func (mb *MicroBlog) HandleBlogRequest(w gemini.ResponseWriter, req *gemini.Requ
} }
func drawPost(builder *strings.Builder, p post) { func drawPost(builder *strings.Builder, p post) {
builder.WriteString("+------------------------------------------+\n") builder.WriteString(fmt.Sprintf("=> / 🖋️ %s post: %s \n", p.Source, p.ID))
content := replaceLinks(p.Content) builder.WriteString(formatContent(p.Content))
builder.WriteString(content) builder.WriteString(fmt.Sprintf("\nposted: %s\n", p.Timestamp.Format("2006-01-02 15:04")))
builder.WriteString("\n") builder.WriteString("\n\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")))
} }
//go:embed microblog.gmi //go:embed microblog.gmi
@ -224,9 +245,6 @@ func (mb *MicroBlog) serveIndex(w gemini.ResponseWriter, req *gemini.Request, pa
w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
var content strings.Builder 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
if pageNum == 1 { if pageNum == 1 {
content.Write([]byte(pageContnet)) content.Write([]byte(pageContnet))
content.WriteString("\n") content.WriteString("\n")

View file

@ -176,7 +176,7 @@ func (c *PocketBaseClient) GetList(
"perPage": {strconv.Itoa(pageSize)}, "perPage": {strconv.Itoa(pageSize)},
"sort": {sort}, "sort": {sort},
"skipTotal": {"true"}, "skipTotal": {"true"},
"filter": {"source = \"nostr\""}, "filter": {"source = \"nostr\" || source = \"mastodon\" || source = \"blue_sky\""},
// TODO: add additional fields like image and tag? // TODO: add additional fields like image and tag?
} }
apiURL := fmt.Sprintf("%s/api/collections/%s/records", c.host, collection) apiURL := fmt.Sprintf("%s/api/collections/%s/records", c.host, collection)

View file

@ -1,8 +1,11 @@
task: rework the micro-blog to be more like station
task: add mastodon and bluysky to microblog
-----------DONE LINE----------- -----------DONE LINE-----------
DONE task: rework the micro-blog to be more like station
DONE task: add mastodon and bluysky to microblog
DONE task: implement dir codeview pages DONE task: implement dir codeview pages
DONE task: implement codbase root codeview pages DONE task: implement codbase root codeview pages
DONE task: add hexidecimal numbering to gemlog DONE task: add hexidecimal numbering to gemlog
DONE task: add request counter DONE task: add request counter
DONE task: embed static pages DONE task: embed static pages
project tracking method inspired by: https://cblgh.org/posts/2025-10-10-the-done-line/