Publish ActivityPub followers collection

This commit is contained in:
Jan-Lukas Else 2022-11-25 20:36:14 +01:00
parent 223ec95487
commit 0df2802c74
5 changed files with 78 additions and 52 deletions

View File

@ -208,22 +208,7 @@ func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) {
}
case ap.CreateType, ap.UpdateType:
if activity.Object.IsObject() {
if object, err := ap.ToObject(activity.Object); err == nil &&
(object.GetType() == ap.NoteType || object.GetType() == ap.ArticleType) &&
(object.To.Contains(ap.PublicNS) || object.CC.Contains(ap.PublicNS)) {
target := object.InReplyTo.GetID().String()
original := object.GetID().String()
name := requestActor.Name.First().Value.String()
if username := requestActor.PreferredUsername.First().String(); name == "" && username != "" {
name = username
}
website := requestActor.GetLink().String()
if actorUrl := requestActor.URL.GetLink(); actorUrl != "" {
website = actorUrl.String()
}
content := object.Content.First().Value.String()
_, _, _ = a.createComment(blog, target, content, name, website, original)
}
a.apOnCreateUpdate(blog, requestActor, activity)
}
case ap.DeleteType, ap.BlockType:
if activity.Object.GetID() == activityActor {
@ -245,6 +230,38 @@ func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func (a *goBlog) apOnCreateUpdate(blog *configBlog, requestActor *ap.Actor, activity *ap.Activity) {
object, err := ap.ToObject(activity.Object)
if err != nil {
return
}
if object.GetType() != ap.NoteType && object.GetType() != ap.ArticleType {
// ignore other objects for now
return
}
visible := true
if !object.To.Contains(ap.PublicNS) && !object.CC.Contains(ap.PublicNS) {
visible = false
}
if replyTarget := object.InReplyTo.GetID().String(); visible && replyTarget != "" && strings.HasPrefix(replyTarget, a.cfg.Server.PublicAddress) {
// It's a reply
original := object.GetID().String()
name := requestActor.Name.First().Value.String()
if username := requestActor.PreferredUsername.First().String(); name == "" && username != "" {
name = username
}
website := requestActor.GetLink().String()
if actorUrl := requestActor.URL.GetLink(); actorUrl != "" {
website = actorUrl.String()
}
content := object.Content.First().Value.String()
_, _, _ = a.createComment(blog, replyTarget, content, name, website, original)
return
}
// Might be a private reply or mention etc.
// TODO: handle them
}
func (a *goBlog) apVerifySignature(r *http.Request, blogIri string) (*ap.Actor, string, int, error) {
verifier, err := httpsig.NewVerifier(r)
if err != nil {
@ -278,6 +295,40 @@ func handleWellKnownHostMeta(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, `<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" type="application/xrd+xml" template="https://`+r.Host+`/.well-known/webfinger?resource={uri}"/></XRD>`)
}
func (a *goBlog) apGetFollowersCollectionId(blogName string, blog *configBlog) ap.IRI {
return ap.IRI(a.apIri(blog) + "/activitypub/followers/" + blogName)
}
func (a *goBlog) apShowFollowers(w http.ResponseWriter, r *http.Request) {
blogName := chi.URLParam(r, "blog")
blog, ok := a.cfg.Blogs[blogName]
if !ok || blog == nil {
a.serveError(w, r, "Blog not found", http.StatusNotFound)
return
}
followers, err := a.db.apGetAllFollowers(blogName)
if err != nil {
a.serveError(w, r, "Failed to get followers", http.StatusInternalServerError)
return
}
if asRequest, ok := r.Context().Value(asRequestKey).(bool); ok && asRequest {
followersCollection := ap.CollectionNew(a.apGetFollowersCollectionId(blogName, blog))
for _, follower := range followers {
followersCollection.Items.Append(ap.IRI(follower.follower))
}
followersCollection.TotalItems = uint(len(followers))
a.serveAPItem(followersCollection, w, r)
return
}
a.render(w, r, a.renderActivityPubFollowers, &renderData{
BlogString: blogName,
Data: &activityPubFollowersRenderData{
apUser: fmt.Sprintf("@%s@%s", blogName, a.cfg.Server.publicHostname),
followers: followers,
},
})
}
func (a *goBlog) apGetRemoteActor(iri, ownBlogIri string) (*ap.Actor, int, error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, iri, strings.NewReader(""))
if err != nil {

View File

@ -12,26 +12,6 @@ import (
"github.com/go-chi/chi/v5"
)
func (a *goBlog) apShowFollowers(w http.ResponseWriter, r *http.Request) {
blogName := chi.URLParam(r, "blog")
blog, ok := a.cfg.Blogs[blogName]
if !ok || blog == nil {
a.serveError(w, r, "Blog not found", http.StatusNotFound)
return
}
followers, err := a.db.apGetAllFollowers(blogName)
if err != nil {
a.serveError(w, r, "Failed to get followers", http.StatusInternalServerError)
return
}
a.render(w, r, a.renderActivityPubFollowers, &renderData{
BlogString: blogName,
Data: &activityPubFollowersRenderData{
followers: followers,
},
})
}
func (a *goBlog) apRemoteFollow(w http.ResponseWriter, r *http.Request) {
blogName := chi.URLParam(r, "blog")
blog, ok := a.cfg.Blogs[blogName]

View File

@ -37,16 +37,7 @@ func (a *goBlog) checkActivityStreamsRequest(next http.Handler) http.Handler {
}
func (a *goBlog) serveActivityStreamsPost(p *post, w http.ResponseWriter, r *http.Request) {
note := a.toAPNote(p)
// Encode
binary, err := jsonld.WithContext(jsonld.IRI(ap.ActivityBaseURI), jsonld.IRI(ap.SecurityContextURI)).Marshal(note)
if err != nil {
a.serveError(w, r, "Encoding failed", http.StatusInternalServerError)
return
}
// Send response
w.Header().Set(contentType, contenttype.ASUTF8)
_ = a.min.Get().Minify(contenttype.AS, w, bytes.NewReader(binary))
a.serveAPItem(a.toAPNote(p), w, r)
}
func (a *goBlog) toAPNote(p *post) *ap.Note {
@ -124,6 +115,8 @@ func (a *goBlog) toApPerson(blog string) *ap.Person {
apBlog.PreferredUsername.Set(ap.DefaultLang, ap.Content(blog))
apBlog.Inbox = ap.IRI(a.getFullAddress("/activitypub/inbox/" + blog))
apBlog.Followers = ap.IRI(a.getFullAddress("/activitypub/followers/" + blog))
apBlog.PublicKey.Owner = apIri
apBlog.PublicKey.ID = ap.IRI(a.apIri(b) + "#main-key")
apBlog.PublicKey.PublicKeyPem = string(pem.EncodeToMemory(&pem.Block{
@ -144,9 +137,12 @@ func (a *goBlog) toApPerson(blog string) *ap.Person {
}
func (a *goBlog) serveActivityStreams(blog string, w http.ResponseWriter, r *http.Request) {
person := a.toApPerson(blog)
a.serveAPItem(a.toApPerson(blog), w, r)
}
func (a *goBlog) serveAPItem(item any, w http.ResponseWriter, r *http.Request) {
// Encode
binary, err := jsonld.WithContext(jsonld.IRI(ap.ActivityBaseURI), jsonld.IRI(ap.SecurityContextURI)).Marshal(person)
binary, err := jsonld.WithContext(jsonld.IRI(ap.ActivityBaseURI), jsonld.IRI(ap.SecurityContextURI)).Marshal(item)
if err != nil {
a.serveError(w, r, "Encoding failed", http.StatusInternalServerError)
return

View File

@ -42,9 +42,7 @@ func (a *goBlog) activityPubRouter(r chi.Router) {
if ap := a.cfg.ActivityPub; ap != nil && ap.Enabled {
r.Route("/activitypub", func(r chi.Router) {
r.Post("/inbox/{blog}", a.apHandleInbox)
r.Post("/{blog}/inbox", a.apHandleInbox) // old
r.With(a.authMiddleware).Get("/{blog}/followers", a.apShowFollowers)
r.With(a.authMiddleware).Get("/followers/{blog}", a.apShowFollowers) // old
r.With(a.checkActivityStreamsRequest).Get("/followers/{blog}", a.apShowFollowers)
r.With(a.cacheMiddleware).Get("/remote_follow/{blog}", a.apRemoteFollow)
r.Post("/remote_follow/{blog}", a.apRemoteFollow)
})

3
ui.go
View File

@ -1583,6 +1583,7 @@ func (a *goBlog) renderSettings(hb *htmlbuilder.HtmlBuilder, rd *renderData) {
}
type activityPubFollowersRenderData struct {
apUser string
followers []*apFollower
}
@ -1603,7 +1604,7 @@ func (a *goBlog) renderActivityPubFollowers(hb *htmlbuilder.HtmlBuilder, rd *ren
hb.WriteElementOpen("h1")
hb.WriteEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "apfollowers"))
hb.WriteEscaped(": ")
hb.WriteEscaped(rd.BlogString)
hb.WriteEscaped(aprd.apUser)
hb.WriteElementClose("h1")
// List followers