mirror of https://github.com/jlelse/GoBlog
Implement ActivityPub remote follow form
This commit is contained in:
parent
35dddbd852
commit
66484fef88
|
@ -1,8 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/carlmjohnson/requests"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
|
@ -25,3 +31,67 @@ func (a *goBlog) apShowFollowers(w http.ResponseWriter, r *http.Request) {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (a *goBlog) apRemoteFollow(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
|
||||
}
|
||||
if user := r.FormValue("user"); user != "" {
|
||||
// Parse instance
|
||||
userParts := strings.Split(user, "@")
|
||||
if len(userParts) < 2 {
|
||||
a.serveError(w, r, "User must be of the form user@example.org or @user@example.org", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
user = userParts[len(userParts)-2]
|
||||
instance := userParts[len(userParts)-1]
|
||||
if user == "" || instance == "" {
|
||||
a.serveError(w, r, "User or instance are empty", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Get webfinger
|
||||
type webfingerLinkType struct {
|
||||
Rel string `json:"rel"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
type webfingerType struct {
|
||||
Links []*webfingerLinkType `json:"links"`
|
||||
}
|
||||
webfinger := &webfingerType{}
|
||||
err := requests.URL(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s@%s", instance, user, instance)).
|
||||
Client(a.httpClient).
|
||||
UserAgent(appUserAgent).
|
||||
Handle(func(resp *http.Response) error {
|
||||
defer resp.Body.Close()
|
||||
return json.NewDecoder(io.LimitReader(resp.Body, 1000*1000)).Decode(webfinger)
|
||||
}).
|
||||
Fetch(r.Context())
|
||||
if err != nil {
|
||||
a.serveError(w, r, "Failed to query webfinger", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Check webfinger and find template
|
||||
template := ""
|
||||
for _, link := range webfinger.Links {
|
||||
if link.Rel == "http://ostatus.org/schema/1.0/subscribe" {
|
||||
template = link.Template
|
||||
break
|
||||
}
|
||||
}
|
||||
if template == "" {
|
||||
a.serveError(w, r, "Instance does not support subscribe schema version 1.0", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Build redirect
|
||||
redirect := strings.ReplaceAll(template, "{uri}", url.PathEscape(a.apIri(blog)))
|
||||
http.Redirect(w, r, redirect, http.StatusFound)
|
||||
return
|
||||
}
|
||||
// Render remote follow form
|
||||
a.render(w, r, a.renderActivityPubRemoteFollow, &renderData{
|
||||
BlogString: blogName,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -42,8 +42,11 @@ 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)
|
||||
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.cacheMiddleware).Get("/remote_follow/{blog}", a.apRemoteFollow)
|
||||
r.Post("/remote_follow/{blog}", a.apRemoteFollow)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(cacheLoggedIn, a.cacheMiddleware)
|
||||
|
|
|
@ -23,6 +23,8 @@ editorpostdesc: "💡 Leere Parameter werden automatisch entfernt. Mehr möglich
|
|||
editorusetemplate: "Benutze Vorlage"
|
||||
emailopt: "E-Mail (optional)"
|
||||
fileuses: "Datei-Verwendungen"
|
||||
follow: "Folgen"
|
||||
followusingactivitypub: "Mit ActivityPub folgen"
|
||||
general: "Allgemein"
|
||||
gentts: "Text-To-Speech-Audio erzeugen"
|
||||
gpxhelper: "GPX-Helfer"
|
||||
|
|
|
@ -30,6 +30,8 @@ editorusetemplate: "Use template"
|
|||
emailopt: "Email (optional)"
|
||||
feed: "Feed"
|
||||
fileuses: "file uses"
|
||||
follow: "Follow"
|
||||
followusingactivitypub: "Follow using ActivityPub"
|
||||
general: "General"
|
||||
gentts: "Generate Text-To-Speech audio"
|
||||
gpxhelper: "GPX helper"
|
||||
|
|
27
ui.go
27
ui.go
|
@ -1631,6 +1631,33 @@ func (a *goBlog) renderActivityPubFollowers(hb *htmlbuilder.HtmlBuilder, rd *ren
|
|||
}
|
||||
hb.WriteElementClose("tbody")
|
||||
hb.WriteElementClose("table")
|
||||
|
||||
hb.WriteElementClose("main")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (a *goBlog) renderActivityPubRemoteFollow(hb *htmlbuilder.HtmlBuilder, rd *renderData) {
|
||||
a.renderBase(
|
||||
hb, rd,
|
||||
func(hb *htmlbuilder.HtmlBuilder) {
|
||||
a.renderTitleTag(hb, rd.Blog, a.ts.GetTemplateStringVariant(rd.Blog.Lang, "apfollowers"))
|
||||
},
|
||||
func(hb *htmlbuilder.HtmlBuilder) {
|
||||
hb.WriteElementOpen("main")
|
||||
|
||||
// Title
|
||||
hb.WriteElementOpen("h1")
|
||||
hb.WriteEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "followusingactivitypub"))
|
||||
hb.WriteElementClose("h1")
|
||||
|
||||
// Form
|
||||
hb.WriteElementOpen("form", "class", "fw p", "method", "post")
|
||||
hb.WriteElementOpen("input", "type", "text", "name", "user", "placeholder", "user@example.org")
|
||||
hb.WriteElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "follow"))
|
||||
hb.WriteElementClose("form")
|
||||
|
||||
hb.WriteElementClose("main")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue