package microblog import ( "fmt" "sort" "strings" "time" gemini "github.com/kulak/gemini" ) // Post represents a single blog post type Post struct { ID string Title string Content string Author string Timestamp time.Time } // MicroBlog manages blog posts type MicroBlog struct { posts []Post } // NewMicroBlog creates a new microblog instance func NewMicroBlog() *MicroBlog { mb := &MicroBlog{ posts: make([]Post, 0), } // Add some sample posts mb.addSamplePosts() return mb } // 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), }, } mb.posts = append(mb.posts, samplePosts...) } // 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(), } 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 } // GetRecentPosts returns the most recent posts func (mb *MicroBlog) GetRecentPosts(limit int) []Post { // Sort posts by timestamp (newest first) sortedPosts := make([]Post, len(mb.posts)) copy(sortedPosts, mb.posts) sort.Slice(sortedPosts, func(i, j int) bool { return sortedPosts[i].Timestamp.After(sortedPosts[j].Timestamp) }) if limit > 0 && len(sortedPosts) > limit { return sortedPosts[:limit] } return sortedPosts } // HandleBlogRequest handles Gemini requests for the microblog func (mb *MicroBlog) HandleBlogRequest(w gemini.ResponseWriter, req *gemini.Request) { path := req.URL.Path switch { case path == "/blog" || path == "/blog/": mb.serveBlogIndex(w, req) case strings.HasPrefix(path, "/blog/post/"): postID := strings.TrimPrefix(path, "/blog/post/") mb.servePost(w, req, postID) case path == "/blog/new": mb.serveNewPostForm(w, req) default: w.WriteStatusMsg(gemini.StatusNotFound, "Blog page not found") } } // 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") var content strings.Builder content.WriteString("# Gemini Microblog\n\n") posts := mb.GetRecentPosts(10) if len(posts) == 0 { content.WriteString("No posts yet. Be the first to write something!\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"))) } } content.WriteString("## Actions\n\n") content.WriteString("=> /blog/new Write a new post\n") content.WriteString("=> / Back to home\n") w.WriteBody([]byte(content.String())) } // servePost serves a single blog post func (mb *MicroBlog) servePost(w gemini.ResponseWriter, req *gemini.Request, postID string) { post, found := mb.GetPost(postID) if !found { w.WriteStatusMsg(gemini.StatusNotFound, "Post not found") return } w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") var content strings.Builder content.WriteString(fmt.Sprintf("# %s\n\n", post.Title)) content.WriteString(fmt.Sprintf("By %s on %s\n\n", post.Author, post.Timestamp.Format("2006-01-02 15:04"))) content.WriteString("---\n\n") content.WriteString(post.Content) content.WriteString("\n\n---\n\n") content.WriteString("=> /blog Back to blog\n") content.WriteString("=> / Back to home\n") w.WriteBody([]byte(content.String())) } // serveNewPostForm serves the form for creating a new post func (mb *MicroBlog) serveNewPostForm(w gemini.ResponseWriter, req *gemini.Request) { // Check if user is authenticated if req.Certificate() == nil { w.WriteStatusMsg(gemini.StatusCertRequired, "Authentication required to create posts") return } w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") var content strings.Builder content.WriteString("# Write a New Post\n\n") content.WriteString("To create a new post, use the Titan protocol:\n\n") content.WriteString("titan://your-server/blog/create;mime=text/plain;token=your-auth-token\n\n") content.WriteString("Format your post as:\n") content.WriteString("Title: Your Post Title\n") content.WriteString("Content: Your post content goes here...\n\n") content.WriteString("=> /blog Back to blog\n") w.WriteBody([]byte(content.String())) } // parsePostData parses the post data from Titan payload func (mb *MicroBlog) parsePostData(data string) (title, content string) { lines := strings.Split(data, "\n") for _, line := range lines { line = strings.TrimSpace(line) if strings.HasPrefix(line, "Title:") { title = strings.TrimSpace(strings.TrimPrefix(line, "Title:")) } else if strings.HasPrefix(line, "Content:") { content = strings.TrimSpace(strings.TrimPrefix(line, "Content:")) } } return title, content }