Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

authored by

bnewbold and committed by
GitHub
3e3a72a3 edc6bdb4

+103
+99
bskyweb/cmd/bskyweb/rss.go
··· 1 + package main 2 + 3 + import ( 4 + "fmt" 5 + "net/http" 6 + 7 + appbsky "github.com/bluesky-social/indigo/api/bsky" 8 + "github.com/bluesky-social/indigo/atproto/syntax" 9 + 10 + "github.com/labstack/echo/v4" 11 + ) 12 + 13 + // We don't actually populate the title for "posts". 14 + // Some background: https://book.micro.blog/rss-for-microblogs/ 15 + type Item struct { 16 + Title string `xml:"title,omitempty"` 17 + Link string `xml:"link,omitempty"` 18 + Description string `xml:"description,omitempty"` 19 + PubDate string `xml:"pubDate,omitempty"` 20 + Author string `xml:"author,omitempty"` 21 + GUID string `xml:"guid,omitempty"` 22 + } 23 + 24 + type rss struct { 25 + Version string `xml:"version,attr"` 26 + Description string `xml:"channel>description,omitempty"` 27 + Link string `xml:"channel>link"` 28 + Title string `xml:"channel>title"` 29 + 30 + Item []Item `xml:"channel>item"` 31 + } 32 + 33 + func (srv *Server) WebProfileRSS(c echo.Context) error { 34 + ctx := c.Request().Context() 35 + 36 + didParam := c.Param("did") 37 + did, err := syntax.ParseDID(didParam) 38 + if err != nil { 39 + return echo.NewHTTPError(400, fmt.Sprintf("not a valid DID: %s", didParam)) 40 + } 41 + 42 + // check that public view is Ok 43 + pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, did.String()) 44 + if err != nil { 45 + return echo.NewHTTPError(404, fmt.Sprintf("account not found: %s", did)) 46 + } 47 + for _, label := range pv.Labels { 48 + if label.Src == pv.Did && label.Val == "!no-unauthenticated" { 49 + return echo.NewHTTPError(403, fmt.Sprintf("account does not allow public views: %s", did)) 50 + } 51 + } 52 + 53 + af, err := appbsky.FeedGetAuthorFeed(ctx, srv.xrpcc, did.String(), "", "", 30) 54 + if err != nil { 55 + log.Warn("failed to fetch author feed", "did", did, "err", err) 56 + return err 57 + } 58 + 59 + posts := []Item{} 60 + for _, p := range af.Feed { 61 + // only include author's own posts in RSS 62 + if p.Post.Author.Did != pv.Did { 63 + continue 64 + } 65 + aturi, err := syntax.ParseATURI(p.Post.Uri) 66 + if err != nil { 67 + return err 68 + } 69 + rec := p.Post.Record.Val.(*appbsky.FeedPost) 70 + // only top-level posts in RSS (no replies) 71 + if rec.Reply != nil { 72 + continue 73 + } 74 + posts = append(posts, Item{ 75 + Link: fmt.Sprintf("https://bsky.app/profile/%s/post/%s", pv.Handle, aturi.RecordKey().String()), 76 + Description: rec.Text, 77 + PubDate: rec.CreatedAt, 78 + Author: "@" + pv.Handle, 79 + GUID: aturi.String(), 80 + }) 81 + } 82 + 83 + title := "@" + pv.Handle 84 + if pv.DisplayName != nil { 85 + title = title + " - " + *pv.DisplayName 86 + } 87 + desc := "" 88 + if pv.Description != nil { 89 + desc = *pv.Description 90 + } 91 + feed := &rss{ 92 + Version: "2.0", 93 + Description: desc, 94 + Link: fmt.Sprintf("https://bsky.app/profile/%s", pv.Handle), 95 + Title: title, 96 + Item: posts, 97 + } 98 + return c.XML(http.StatusOK, feed) 99 + }
+3
bskyweb/cmd/bskyweb/server.go
··· 209 209 e.GET("/profile/:handle/feed/:rkey", server.WebGeneric) 210 210 e.GET("/profile/:handle/feed/:rkey/liked-by", server.WebGeneric) 211 211 212 + // profile RSS feed (DID not handle) 213 + e.GET("/profile/:did/rss", server.WebProfileRSS) 214 + 212 215 // post endpoints; only first populates info 213 216 e.GET("/profile/:handle/post/:rkey", server.WebPost) 214 217 e.GET("/profile/:handle/post/:rkey/liked-by", server.WebGeneric)
+1
bskyweb/templates/profile.html
··· 34 34 {% endif %} 35 35 <meta name="twitter:label1" content="Account DID"> 36 36 <meta name="twitter:value1" content="{{ profileView.Did }}"> 37 + <link rel="alternate" type="application/rss+xml" href="/profile/{{ profileView.Did }}/rss"> 37 38 {% endif -%} 38 39 {%- endblock %} 39 40