diff --git a/activityPub.go b/activityPub.go index 0163cf2..9018d31 100644 --- a/activityPub.go +++ b/activityPub.go @@ -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, ``) } +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 { diff --git a/activityPubTools.go b/activityPubTools.go index 59dd0cd..dd9b9fb 100644 --- a/activityPubTools.go +++ b/activityPubTools.go @@ -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] diff --git a/activityStreams.go b/activityStreams.go index 5ac64f1..af0756f 100644 --- a/activityStreams.go +++ b/activityStreams.go @@ -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 diff --git a/httpRouters.go b/httpRouters.go index 6acd461..7f5290d 100644 --- a/httpRouters.go +++ b/httpRouters.go @@ -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) }) diff --git a/ui.go b/ui.go index 08ad4db..580fe4f 100644 --- a/ui.go +++ b/ui.go @@ -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