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 XMLzio/stable
parent
edc6bdb4d6
commit
3e3a72a366
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
Loading…
Reference in New Issue