diff options
author | bnewbold <bnewbold@robocracy.org> | 2023-12-18 23:52:39 +0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-18 23:52:39 +0400 |
commit | 3e3a72a366f2b05ed987af67736ec3f0e2f60272 (patch) | |
tree | c424336d6310fcca4d9319ea3a7325d12781c3b1 /bskyweb | |
parent | edc6bdb4d6e052778022bee997137dbf392c85c9 (diff) | |
download | voidsky-3e3a72a366f2b05ed987af67736ec3f0e2f60272.tar.zst |
basic public RSS feed for profiles (#2229)
* web: initial implementation of profile RSS feed * re-work RSS feed to use DID in URL, not handle Shouldn't have RSS feeds break when folks change handle. * rss: tweak XML
Diffstat (limited to 'bskyweb')
-rw-r--r-- | bskyweb/cmd/bskyweb/rss.go | 99 | ||||
-rw-r--r-- | bskyweb/cmd/bskyweb/server.go | 3 | ||||
-rw-r--r-- | bskyweb/templates/profile.html | 1 |
3 files changed, 103 insertions, 0 deletions
diff --git a/bskyweb/cmd/bskyweb/rss.go b/bskyweb/cmd/bskyweb/rss.go new file mode 100644 index 000000000..f7caf8fe7 --- /dev/null +++ b/bskyweb/cmd/bskyweb/rss.go @@ -0,0 +1,99 @@ +package main + +import ( + "fmt" + "net/http" + + appbsky "github.com/bluesky-social/indigo/api/bsky" + "github.com/bluesky-social/indigo/atproto/syntax" + + "github.com/labstack/echo/v4" +) + +// We don't actually populate the title for "posts". +// Some background: https://book.micro.blog/rss-for-microblogs/ +type Item struct { + Title string `xml:"title,omitempty"` + Link string `xml:"link,omitempty"` + Description string `xml:"description,omitempty"` + PubDate string `xml:"pubDate,omitempty"` + Author string `xml:"author,omitempty"` + GUID string `xml:"guid,omitempty"` +} + +type rss struct { + Version string `xml:"version,attr"` + Description string `xml:"channel>description,omitempty"` + Link string `xml:"channel>link"` + Title string `xml:"channel>title"` + + Item []Item `xml:"channel>item"` +} + +func (srv *Server) WebProfileRSS(c echo.Context) error { + ctx := c.Request().Context() + + didParam := c.Param("did") + did, err := syntax.ParseDID(didParam) + if err != nil { + return echo.NewHTTPError(400, fmt.Sprintf("not a valid DID: %s", didParam)) + } + + // check that public view is Ok + pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, did.String()) + if err != nil { + return echo.NewHTTPError(404, fmt.Sprintf("account not found: %s", did)) + } + for _, label := range pv.Labels { + if label.Src == pv.Did && label.Val == "!no-unauthenticated" { + return echo.NewHTTPError(403, fmt.Sprintf("account does not allow public views: %s", did)) + } + } + + af, err := appbsky.FeedGetAuthorFeed(ctx, srv.xrpcc, did.String(), "", "", 30) + if err != nil { + log.Warn("failed to fetch author feed", "did", did, "err", err) + return err + } + + posts := []Item{} + for _, p := range af.Feed { + // only include author's own posts in RSS + if p.Post.Author.Did != pv.Did { + continue + } + aturi, err := syntax.ParseATURI(p.Post.Uri) + if err != nil { + return err + } + rec := p.Post.Record.Val.(*appbsky.FeedPost) + // only top-level posts in RSS (no replies) + if rec.Reply != nil { + continue + } + posts = append(posts, Item{ + Link: fmt.Sprintf("https://bsky.app/profile/%s/post/%s", pv.Handle, aturi.RecordKey().String()), + Description: rec.Text, + PubDate: rec.CreatedAt, + Author: "@" + pv.Handle, + GUID: aturi.String(), + }) + } + + title := "@" + pv.Handle + if pv.DisplayName != nil { + title = title + " - " + *pv.DisplayName + } + desc := "" + if pv.Description != nil { + desc = *pv.Description + } + feed := &rss{ + Version: "2.0", + Description: desc, + Link: fmt.Sprintf("https://bsky.app/profile/%s", pv.Handle), + Title: title, + Item: posts, + } + return c.XML(http.StatusOK, feed) +} diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 7760860f7..5d9a481fe 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -209,6 +209,9 @@ func serve(cctx *cli.Context) error { e.GET("/profile/:handle/feed/:rkey", server.WebGeneric) e.GET("/profile/:handle/feed/:rkey/liked-by", server.WebGeneric) + // profile RSS feed (DID not handle) + e.GET("/profile/:did/rss", server.WebProfileRSS) + // post endpoints; only first populates info e.GET("/profile/:handle/post/:rkey", server.WebPost) e.GET("/profile/:handle/post/:rkey/liked-by", server.WebGeneric) diff --git a/bskyweb/templates/profile.html b/bskyweb/templates/profile.html index d324a265e..71c100327 100644 --- a/bskyweb/templates/profile.html +++ b/bskyweb/templates/profile.html @@ -34,6 +34,7 @@ {% endif %} <meta name="twitter:label1" content="Account DID"> <meta name="twitter:value1" content="{{ profileView.Did }}"> + <link rel="alternate" type="application/rss+xml" href="/profile/{{ profileView.Did }}/rss"> {% endif -%} {%- endblock %} |