From 99bb77153e4d78e2ec04075ab2e7b28aae9ec2f2 Mon Sep 17 00:00:00 2001 From: Michael Demetriou Date: Thu, 10 Oct 2019 15:11:46 +0300 Subject: [PATCH] Handles are saved in `remoteusers` while the links take you to an intermediate page (WIP) that shows the user profile page url --- activitypub.go | 38 ++++++++++++++++++++++++++------------ collections.go | 15 +++++++++++++++ postrender.go | 3 +++ posts.go | 42 +++++++++++++++++++++++++++++++++++++----- routes.go | 1 + 5 files changed, 82 insertions(+), 17 deletions(-) diff --git a/activitypub.go b/activitypub.go index fa25061..081fcea 100644 --- a/activitypub.go +++ b/activitypub.go @@ -26,7 +26,6 @@ import ( "github.com/gorilla/mux" "github.com/writeas/activity/streams" - "github.com/writeas/activityserve" "github.com/writeas/httpsig" "github.com/writeas/impart" "github.com/writeas/nerds/store" @@ -45,6 +44,7 @@ type RemoteUser struct { ActorID string Inbox string SharedInbox string + Handle string } func (ru *RemoteUser) AsPerson() *activitystreams.Person { @@ -133,7 +133,7 @@ func handleFetchCollectionOutbox(app *App, w http.ResponseWriter, r *http.Reques posts, err := app.db.GetPosts(app.cfg, c, p, false, true, false) for _, pp := range *posts { pp.Collection = res - o := pp.ActivityObject(app.cfg) + o := pp.ActivityObject(app) a := activitystreams.NewCreateActivity(o) ocp.OrderedItems = append(ocp.OrderedItems, *a) } @@ -525,7 +525,7 @@ func deleteFederatedPost(app *App, p *PublicPost, collID int64) error { } p.Collection.hostName = app.cfg.App.Host actor := p.Collection.PersonObject(collID) - na := p.ActivityObject(app.cfg) + na := p.ActivityObject(app) // Add followers p.Collection.ID = collID @@ -571,7 +571,7 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error { } } actor := p.Collection.PersonObject(collID) - na := p.ActivityObject(app.cfg) + na := p.ActivityObject(app) // Add followers p.Collection.ID = collID @@ -626,8 +626,7 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error { // the mentioned users. This might seem wasteful but the code is // cleaner than adding the mentioned users to CC here instead of // in p.ActivityObject() - na = p.ActivityObject(app.cfg) - + na = p.ActivityObject(app) for _, tag := range na.Tag { if tag.Type == "Mention" { activity = activitystreams.NewCreateActivity(na) @@ -638,11 +637,11 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error { // much logic to catch this at the expense of the odd extra request. // I don't believe we'd ever have too many mentions in a single post that this // could become a burden. - remoteActor, err := activityserve.NewRemoteActor(tag.HRef) - if err != nil { - log.Error("Couldn't fetch remote actor", err) - } - err = makeActivityPost(app.cfg.App.Host, actor, remoteActor.GetInbox(), activity) + + fmt.Println(tag.HRef) + fmt.Println("aaa") + remoteUser, err := getRemoteUser(app, tag.HRef) + err = makeActivityPost(app.cfg.App.Host, actor, remoteUser.Inbox, activity) if err != nil { log.Error("Couldn't post! %v", err) } @@ -656,7 +655,7 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error { func getRemoteUser(app *App, actorID string) (*RemoteUser, error) { u := RemoteUser{ActorID: actorID} - err := app.db.QueryRow("SELECT id, inbox, shared_inbox FROM remoteusers WHERE actor_id = ?", actorID).Scan(&u.ID, &u.Inbox, &u.SharedInbox) + err := app.db.QueryRow("SELECT id, inbox, shared_inbox, handle FROM remoteusers WHERE actor_id = ?", actorID).Scan(&u.ID, &u.Inbox, &u.SharedInbox, &u.Handle) switch { case err == sql.ErrNoRows: return nil, impart.HTTPError{http.StatusNotFound, "No remote user with that ID."} @@ -668,6 +667,21 @@ func getRemoteUser(app *App, actorID string) (*RemoteUser, error) { return &u, nil } +// getRemoteUserFromHandle retrieves the profile page of a remote user +// from the @user@server.tld handle +func getRemoteUserFromHandle(app *App, handle string) (*RemoteUser, error) { + u := RemoteUser{Handle: handle} + err := app.db.QueryRow("SELECT id, actor_id, inbox, shared_inbox FROM remoteusers WHERE handle = ?", handle).Scan(&u.ID, &u.ActorID, &u.Inbox, &u.SharedInbox) + switch { + case err == sql.ErrNoRows: + return nil, impart.HTTPError{http.StatusNotFound, "No remote user with that handle."} + case err != nil: + log.Error("Couldn't get remote user %s: %v", handle, err) + return nil, err + } + return &u, nil +} + func getActor(app *App, actorIRI string) (*activitystreams.Person, *RemoteUser, error) { log.Info("Fetching actor %s locally", actorIRI) actor := &activitystreams.Person{} diff --git a/collections.go b/collections.go index c095ecb..9ce9d8e 100644 --- a/collections.go +++ b/collections.go @@ -820,6 +820,21 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro return err } +func handleViewMention(app *App, w http.ResponseWriter, r *http.Request) error { + vars := mux.Vars(r) + handle := vars["handle"] + + remoteUser, err := getRemoteUserFromHandle(app, handle) + if err != nil { + log.Error("Couldn't find this user in our database "+handle, err) + return err + } + + w.Write([]byte("go to " + remoteUser.ActorID)) + + return nil +} + func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) error { vars := mux.Vars(r) tag := vars["tag"] diff --git a/postrender.go b/postrender.go index 83fb5ad..e31c462 100644 --- a/postrender.go +++ b/postrender.go @@ -34,6 +34,7 @@ var ( titleElementReg = regexp.MustCompile("") hashtagReg = regexp.MustCompile(`{{\[\[\|\|([^|]+)\|\|\]\]}}`) markeddownReg = regexp.MustCompile("

(.+)

") + mentionReg = regexp.MustCompile(`@[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\b`) ) func (p *Post) formatContent(cfg *config.Config, c *Collection, isOwner bool) { @@ -82,6 +83,8 @@ func applyMarkdownSpecial(data []byte, skipNoFollow bool, baseURL string, cfg *c tagPrefix = "/read/t/" } md = []byte(hashtagReg.ReplaceAll(md, []byte("#$1"))) + handlePrefix := baseURL + "mention:" + md = []byte(mentionReg.ReplaceAll(md, []byte("$0"))) } // Strip out bad HTML policy := getSanitizationPolicy() diff --git a/posts.go b/posts.go index 4b6fcc1..3eeb232 100644 --- a/posts.go +++ b/posts.go @@ -25,6 +25,7 @@ import ( "github.com/guregu/null/zero" "github.com/kylemcc/twitter-text-go/extract" "github.com/microcosm-cc/bluemonday" + "github.com/writeas/activityserve" stripmd "github.com/writeas/go-strip-markdown" "github.com/writeas/impart" "github.com/writeas/monday" @@ -35,7 +36,6 @@ import ( "github.com/writeas/web-core/i18n" "github.com/writeas/web-core/log" "github.com/writeas/web-core/tags" - "github.com/writeas/writefreely/config" "github.com/writeas/writefreely/page" "github.com/writeas/writefreely/parse" ) @@ -1033,7 +1033,7 @@ func fetchPost(app *App, w http.ResponseWriter, r *http.Request) error { } p.Collection = &CollectionObj{Collection: *coll} - po := p.ActivityObject(app.cfg) + po := p.ActivityObject(app) po.Context = []interface{}{activitystreams.Namespace} return impart.RenderActivityJSON(w, po, http.StatusOK) } @@ -1068,7 +1068,8 @@ func (p *PublicPost) CanonicalURL() string { return p.Collection.CanonicalURL() + p.Slug.String } -func (p *PublicPost) ActivityObject(cfg *config.Config) *activitystreams.Object { +func (p *PublicPost) ActivityObject(app *App) *activitystreams.Object { + cfg := app.cfg o := activitystreams.NewArticleObject() o.ID = p.Collection.FederatedAPIBase() + "api/posts/" + p.ID o.Published = p.Created @@ -1117,7 +1118,38 @@ func (p *PublicPost) ActivityObject(cfg *config.Config) *activitystreams.Object mentions := mentionRegex.FindAllString(content, -1) for _, handle := range mentions { - actorIRI := RemoteLookup(handle) + var actorIRI string + remoteuser, errRemoteUser := getRemoteUserFromHandle(app, handle) + if errRemoteUser != nil { + // can't find using handle in the table but the table may already have this user without + // handle from a previous version + actorIRI = RemoteLookup(handle) + _, errRemoteUser := getRemoteUser(app, actorIRI) + // if it exists then we need to update the handle + if errRemoteUser == nil { + // query := "UPDATE remoteusers SET handle='" + handle + "' WHERE actor_id='" + iri + "';" + // log.Info(query) + _, err := app.db.Exec("UPDATE remoteusers SET handle=? WHERE actor_id=?;", handle, actorIRI) + if err != nil { + log.Error("Can't update handle (" + handle + ") in database for user " + actorIRI) + } + } else { + // this probably means we don't have the user in the table so let's try to insert it + // here we need to ask the server for the inboxes + remoteActor, err := activityserve.NewRemoteActor(actorIRI) + if err != nil { + log.Error("Couldn't fetch remote actor", err) + } + fmt.Println(actorIRI, remoteActor.GetInbox(), remoteActor.GetSharedInbox(), handle) + _, err = app.db.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, handle) VALUES( ?, ?, ?, ?)", actorIRI, remoteActor.GetInbox(), remoteActor.GetSharedInbox(), handle) + if err != nil { + log.Error("Can't insert remote user in database", err) + return nil + } + } + } else { + actorIRI = remoteuser.ActorID + } mentionedUsers[handle] = actorIRI } @@ -1379,7 +1411,7 @@ Are you sure it was ever here?`, return ErrCollectionPageNotFound } p.extractData() - ap := p.ActivityObject(app.cfg) + ap := p.ActivityObject(app) ap.Context = []interface{}{activitystreams.Namespace} return impart.RenderActivityJSON(w, ap, http.StatusOK) } else { diff --git a/routes.go b/routes.go index 0113e93..4c26f47 100644 --- a/routes.go +++ b/routes.go @@ -184,6 +184,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { func RouteCollections(handler *Handler, r *mux.Router) { r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader)) + r.HandleFunc("/mention:{handle}", handler.Web(handleViewMention, UserLevelReader)) r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/tags/{tag}", handler.Web(handleViewCollectionTag, UserLevelReader))