From 5219a7b9e709103c715375ee6a65a700f6e28b5f Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Mon, 24 May 2021 15:50:20 +0200 Subject: [PATCH] start on federating faves --- internal/gtsmodel/statusfave.go | 2 ++ internal/message/fromclientapiprocess.go | 24 ++++++++++++++++++++++++ internal/message/statusprocess.go | 12 +++++++++++- internal/util/regexes.go | 13 +++++++++---- internal/util/uri.go | 23 +++++++++++++++++++++++ 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/internal/gtsmodel/statusfave.go b/internal/gtsmodel/statusfave.go index 9fb92b931..00e927a59 100644 --- a/internal/gtsmodel/statusfave.go +++ b/internal/gtsmodel/statusfave.go @@ -32,6 +32,8 @@ type StatusFave struct { TargetAccountID string `pg:",notnull"` // database id of the status that has been 'faved' StatusID string `pg:",notnull"` + // ActivityPub URI of this fave + URI string `pg:",notnull"` // FavedStatus is the status being interacted with. It won't be put or retrieved from the db, it's just for conveniently passing a pointer around. FavedStatus *Status `pg:"-"` diff --git a/internal/message/fromclientapiprocess.go b/internal/message/fromclientapiprocess.go index e91bd6ce4..2874bb8db 100644 --- a/internal/message/fromclientapiprocess.go +++ b/internal/message/fromclientapiprocess.go @@ -60,6 +60,9 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error } return p.federateFollow(follow, clientMsg.OriginAccount, clientMsg.TargetAccount) + case gtsmodel.ActivityStreamsLike: + // CREATE LIKE/FAVE + fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) } case gtsmodel.ActivityStreamsUpdate: // UPDATE @@ -214,3 +217,24 @@ func (p *processor) federateAcceptFollowRequest(follow *gtsmodel.Follow, originA _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, accept) return err } + +func (p *processor) federateFave(fave *gtsmodel.StatusFave, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { + // if both accounts are local there's nothing to do here + if originAccount.Domain == "" && targetAccount.Domain == "" { + return nil + } + + // create the AS fave + asFave, err := p.tc.FaveToAS(fave, originAccount, targetAccount) + if err != nil { + return fmt.Errorf("federateFave: error converting fave to as format: %s", err) + } + + outboxIRI, err := url.Parse(originAccount.OutboxURI) + if err != nil { + return fmt.Errorf("federateFave: error parsing outboxURI %s: %s", originAccount.OutboxURI, err) + } + + _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, asFave) + return err +} diff --git a/internal/message/statusprocess.go b/internal/message/statusprocess.go index 86a07eb4f..38c60fe8c 100644 --- a/internal/message/statusprocess.go +++ b/internal/message/statusprocess.go @@ -186,10 +186,20 @@ func (p *processor) StatusFave(authed *oauth.Auth, targetStatusID string) (*apim } // it's visible! it's faveable! so let's fave the FUCK out of it - _, err = p.db.FaveStatus(targetStatus, authed.Account.ID) + gtsFave, err := p.db.FaveStatus(targetStatus, authed.Account.ID) if err != nil { return nil, fmt.Errorf("error faveing status: %s", err) } + gtsFave.FavedStatus = targetStatus // pin a pointer to the target status to the fave for convenience later on + + // send the new fave through the processor channel for federation etc + p.fromClientAPI <- gtsmodel.FromClientAPI{ + APObjectType: gtsmodel.ActivityStreamsLike, + APActivityType: gtsmodel.ActivityStreamsCreate, + GTSModel: gtsFave, + OriginAccount: authed.Account, + TargetAccount: targetAccount, + } var boostOfStatus *gtsmodel.Status if targetStatus.BoostOfID != "" { diff --git a/internal/util/regexes.go b/internal/util/regexes.go index adab8c87f..55773c370 100644 --- a/internal/util/regexes.go +++ b/internal/util/regexes.go @@ -85,13 +85,18 @@ var ( // followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following followingPathRegex = regexp.MustCompile(followingPathRegexString) - likedPathRegexString = fmt.Sprintf(`^/?%s/%s/%s$`, UsersPath, usernameRegexString, LikedPath) - // followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/liked - likedPathRegex = regexp.MustCompile(likedPathRegexString) - // see https://ihateregex.io/expr/uuid/ uuidRegexString = `[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}` + likedPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, LikedPath) + // likedPathRegex parses a path that validates and captures the username part from eg /users/example_username/liked + likedPathRegex = regexp.MustCompile(likedPathRegexString) + + likePathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, LikedPath, uuidRegexString) + // likePathRegex parses a path that validates and captures the username part and the uuid part + // from eg /users/example_username/liked/123e4567-e89b-12d3-a456-426655440000. + likePathRegex = regexp.MustCompile(likePathRegexString) + statusesPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, StatusesPath, uuidRegexString) // statusesPathRegex parses a path that validates and captures the username part and the uuid part // from eg /users/example_username/statuses/123e4567-e89b-12d3-a456-426655440000. diff --git a/internal/util/uri.go b/internal/util/uri.go index 0ee4a5120..4dfb7bc45 100644 --- a/internal/util/uri.go +++ b/internal/util/uri.go @@ -113,6 +113,12 @@ func GenerateURIForFollow(username string, protocol string, host string) string return fmt.Sprintf("%s://%s/%s/%s/%s", protocol, host, UsersPath, FollowPath, uuid.NewString()) } +// GenerateURIForFollow returns the AP URI for a new like/fave -- something like: +// https://example.org/users/whatever_user/liked/41c7f33f-1060-48d9-84df-38dcb13cf0d8 +func GenerateURIForLike(username string, protocol string, host string) string { + return fmt.Sprintf("%s://%s/%s/%s/%s", protocol, host, UsersPath, LikedPath, uuid.NewString()) +} + // GenerateURIsForAccount throws together a bunch of URIs for the given username, with the given protocol and host. func GenerateURIsForAccount(username string, protocol string, host string) *UserURIs { // The below URLs are used for serving web requests @@ -183,6 +189,11 @@ func IsLikedPath(id *url.URL) bool { return likedPathRegex.MatchString(strings.ToLower(id.Path)) } +// IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked/SOME_UUID_OF_A_STATUS +func IsLikePath(id *url.URL) bool { + return likePathRegex.MatchString(strings.ToLower(id.Path)) +} + // IsStatusesPath returns true if the given URL path corresponds to eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS func IsStatusesPath(id *url.URL) bool { return statusesPathRegex.MatchString(strings.ToLower(id.Path)) @@ -254,3 +265,15 @@ func ParseFollowingPath(id *url.URL) (username string, err error) { username = matches[1] return } + +// ParseLikedPath returns the username and uuid from a path such as /users/example_username/liked/SOME_UUID_OF_A_STATUS +func ParseLikedPath(id *url.URL) (username string, uuid string, err error) { + matches := likePathRegex.FindStringSubmatch(id.Path) + if len(matches) != 3 { + err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches)) + return + } + username = matches[1] + uuid = matches[2] + return +}