Compare commits
No commits in common. "a88994add42778cd41e58fe1f3301518f69e5186" and "8c28d2ee0c5195284134e29e05880c1b5cfdc6fa" have entirely different histories.
a88994add4
...
8c28d2ee0c
4 changed files with 83 additions and 103 deletions
|
|
@ -6,8 +6,9 @@ clearnet version:
|
||||||
|
|
||||||
=> https://travisshears.com/micro-blog
|
=> https://travisshears.com/micro-blog
|
||||||
|
|
||||||
Here post from nostr, mastodon, and bluesky are rendered.
|
So for it renders posts from:
|
||||||
|
* nostr, id: nprofile1qyxhwumn8ghj7mn0wvhxcmmvqqs9udcv9uhqggjz87js9rtaph4lajlxnxsvwvm7zwdjt6etzyk52rgeg4wrz
|
||||||
|
|
||||||
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.
|
I plan to add more platforms in the future:
|
||||||
=> https://bsky.app/profile/travisshears.bsky.social @travisshears on Bluesky
|
* mastodon, id: dice.camp/@travisshears
|
||||||
=> https://dice.camp/@travisshears @travisshears on Mastodon via dice.camp
|
* bluesky, id: @travisshears.bsky.social
|
||||||
|
|
|
||||||
|
|
@ -61,34 +61,59 @@ func NewMicroBlog(pbClient *pocketbase.PocketBaseClient) *MicroBlog {
|
||||||
return mb
|
return mb
|
||||||
}
|
}
|
||||||
|
|
||||||
func escapeHashTags(content string) string {
|
// // Add some sample posts
|
||||||
// TODO: maybe prettier way to do this with a code
|
// mb.addSamplePosts()
|
||||||
return strings.ReplaceAll(content, "#", "\\#")
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeHTMLTags(content string) string {
|
// return mb
|
||||||
// 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")
|
|
||||||
|
|
||||||
// Convert <br> to newline
|
// addSamplePosts adds some initial content
|
||||||
reBr := regexp.MustCompile(`(?i)<br\s*/?>`)
|
// func (mb *MicroBlog) addSamplePosts() {
|
||||||
content = reBr.ReplaceAllString(content, "\n")
|
// 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),
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
// Remove any remaining tags
|
// mb.posts = append(mb.posts, samplePosts...)
|
||||||
reTags := regexp.MustCompile(`(?i)<[^>]+>`)
|
// }
|
||||||
content = reTags.ReplaceAllString(content, "")
|
|
||||||
|
|
||||||
// Normalize whitespace: trim and collapse excessive blank lines
|
// AddPost adds a new post to the blog
|
||||||
content = strings.TrimSpace(content)
|
// func (mb *MicroBlog) AddPost(title, content, author string) string {
|
||||||
reMultiNewlines := regexp.MustCompile(`\n{3,}`)
|
// id := fmt.Sprintf("%d", time.Now().Unix())
|
||||||
return reMultiNewlines.ReplaceAllString(content, "\n\n")
|
// post := Post{
|
||||||
|
// 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 {
|
||||||
|
|
@ -96,63 +121,8 @@ func transformNostrPost(p pbPost) post {
|
||||||
return post{}
|
return post{}
|
||||||
}
|
}
|
||||||
content := nostrPost.Content
|
content := nostrPost.Content
|
||||||
content = escapeHashTags(content)
|
content = strings.ReplaceAll(content, "#", "\\#")
|
||||||
return post{
|
// content = strings.ReplaceAll(content, "\t", "\\t")
|
||||||
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,
|
||||||
|
|
@ -177,22 +147,26 @@ func (mb *MicroBlog) GetRecentPosts(limit int, page int) ([]post, error) {
|
||||||
|
|
||||||
var filteredPosts []post
|
var filteredPosts []post
|
||||||
for _, p := range rawPosts {
|
for _, p := range rawPosts {
|
||||||
switch p.Source {
|
if p.Source == SourceNostr {
|
||||||
case SourceNostr:
|
|
||||||
filteredPosts = append(filteredPosts, transformNostrPost(p))
|
filteredPosts = append(filteredPosts, transformNostrPost(p))
|
||||||
case SourceMastodon:
|
|
||||||
filteredPosts = append(filteredPosts, transformMastodonPost(p))
|
// var nostrPost nostrPost
|
||||||
case SourceBlueSky:
|
// if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil {
|
||||||
filteredPosts = append(filteredPosts, transformBlueSkyPost(p))
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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
|
||||||
|
|
@ -230,11 +204,16 @@ 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(fmt.Sprintf("=> / 🖋️ %s post: %s \n", p.Source, p.ID))
|
builder.WriteString("+------------------------------------------+\n")
|
||||||
builder.WriteString(formatContent(p.Content))
|
content := replaceLinks(p.Content)
|
||||||
builder.WriteString(fmt.Sprintf("\nposted: %s\n", p.Timestamp.Format("2006-01-02 15:04")))
|
builder.WriteString(content)
|
||||||
builder.WriteString("\n\n")
|
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")))
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed microblog.gmi
|
//go:embed microblog.gmi
|
||||||
|
|
@ -245,6 +224,9 @@ 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")
|
||||||
|
|
|
||||||
|
|
@ -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\" || source = \"mastodon\" || source = \"blue_sky\""},
|
"filter": {"source = \"nostr\""},
|
||||||
// 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)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
|
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/
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue