summaryrefslogtreecommitdiff
path: root/internal/message
diff options
context:
space:
mode:
authorLibravatar Tobi Smethurst <31960611+tsmethurst@users.noreply.github.com>2021-05-21 15:48:26 +0200
committerLibravatar GitHub <noreply@github.com>2021-05-21 15:48:26 +0200
commitd839f27c306eedebdc7cc0311f35b8856cc2bb24 (patch)
tree7a11a3a641f902991d26771c4d3f8e836a2bce7e /internal/message
parentupdate progress (diff)
downloadgotosocial-d839f27c306eedebdc7cc0311f35b8856cc2bb24.tar.xz
Follows and relationships (#27)
* Follows -- create and undo, both remote and local * Statuses -- federate new posts, including media, attachments, CWs and image descriptions.
Diffstat (limited to 'internal/message')
-rw-r--r--internal/message/accountprocess.go250
-rw-r--r--internal/message/fediprocess.go86
-rw-r--r--internal/message/fromclientapiprocess.go217
-rw-r--r--internal/message/fromcommonprocess.go4
-rw-r--r--internal/message/fromfederatorprocess.go92
-rw-r--r--internal/message/frprocess.go25
-rw-r--r--internal/message/instanceprocess.go3
-rw-r--r--internal/message/processor.go24
-rw-r--r--internal/message/processorutil.go39
9 files changed, 672 insertions, 68 deletions
diff --git a/internal/message/accountprocess.go b/internal/message/accountprocess.go
index a10f6d016..29fd55034 100644
--- a/internal/message/accountprocess.go
+++ b/internal/message/accountprocess.go
@@ -78,6 +78,15 @@ func (p *processor) AccountGet(authed *oauth.Auth, targetAccountID string) (*api
return nil, fmt.Errorf("db error: %s", err)
}
+ // lazily dereference things on the account if it hasn't been done yet
+ var requestingUsername string
+ if authed.Account != nil {
+ requestingUsername = authed.Account.Username
+ }
+ if err := p.dereferenceAccountFields(targetAccount, requestingUsername); err != nil {
+ p.log.WithField("func", "AccountGet").Debugf("dereferencing account: %s", err)
+ }
+
var mastoAccount *apimodel.Account
var err error
if authed.Account != nil && targetAccount.ID == authed.Account.ID {
@@ -285,6 +294,12 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri
return nil, NewErrorInternalError(err)
}
+ // derefence account fields in case we haven't done it already
+ if err := p.dereferenceAccountFields(a, authed.Account.Username); err != nil {
+ // don't bail if we can't fetch them, we'll try another time
+ p.log.WithField("func", "AccountFollowersGet").Debugf("error dereferencing account fields: %s", err)
+ }
+
account, err := p.tc.AccountToMastoPublic(a)
if err != nil {
return nil, NewErrorInternalError(err)
@@ -293,3 +308,238 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri
}
return accounts, nil
}
+
+func (p *processor) AccountFollowingGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode) {
+ blocked, err := p.db.Blocked(authed.Account.ID, targetAccountID)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ if blocked {
+ return nil, NewErrorNotFound(fmt.Errorf("block exists between accounts"))
+ }
+
+ following := []gtsmodel.Follow{}
+ accounts := []apimodel.Account{}
+ if err := p.db.GetFollowingByAccountID(targetAccountID, &following); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ return accounts, nil
+ }
+ return nil, NewErrorInternalError(err)
+ }
+
+ for _, f := range following {
+ blocked, err := p.db.Blocked(authed.Account.ID, f.AccountID)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+ if blocked {
+ continue
+ }
+
+ a := &gtsmodel.Account{}
+ if err := p.db.GetByID(f.TargetAccountID, a); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ continue
+ }
+ return nil, NewErrorInternalError(err)
+ }
+
+ // derefence account fields in case we haven't done it already
+ if err := p.dereferenceAccountFields(a, authed.Account.Username); err != nil {
+ // don't bail if we can't fetch them, we'll try another time
+ p.log.WithField("func", "AccountFollowingGet").Debugf("error dereferencing account fields: %s", err)
+ }
+
+ account, err := p.tc.AccountToMastoPublic(a)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+ accounts = append(accounts, *account)
+ }
+ return accounts, nil
+}
+
+func (p *processor) AccountRelationshipGet(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode) {
+ if authed == nil || authed.Account == nil {
+ return nil, NewErrorForbidden(errors.New("not authed"))
+ }
+
+ gtsR, err := p.db.GetRelationship(authed.Account.ID, targetAccountID)
+ if err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("error getting relationship: %s", err))
+ }
+
+ r, err := p.tc.RelationshipToMasto(gtsR)
+ if err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("error converting relationship: %s", err))
+ }
+
+ return r, nil
+}
+
+func (p *processor) AccountFollowCreate(authed *oauth.Auth, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, ErrorWithCode) {
+ // if there's a block between the accounts we shouldn't create the request ofc
+ blocked, err := p.db.Blocked(authed.Account.ID, form.TargetAccountID)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+ if blocked {
+ return nil, NewErrorNotFound(fmt.Errorf("accountfollowcreate: block exists between accounts"))
+ }
+
+ // make sure the target account actually exists in our db
+ targetAcct := &gtsmodel.Account{}
+ if err := p.db.GetByID(form.TargetAccountID, targetAcct); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ return nil, NewErrorNotFound(fmt.Errorf("accountfollowcreate: account %s not found in the db: %s", form.TargetAccountID, err))
+ }
+ }
+
+ // check if a follow exists already
+ follows, err := p.db.Follows(authed.Account, targetAcct)
+ if err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("accountfollowcreate: error checking follow in db: %s", err))
+ }
+ if follows {
+ // already follows so just return the relationship
+ return p.AccountRelationshipGet(authed, form.TargetAccountID)
+ }
+
+ // check if a follow exists already
+ followRequested, err := p.db.FollowRequested(authed.Account, targetAcct)
+ if err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("accountfollowcreate: error checking follow request in db: %s", err))
+ }
+ if followRequested {
+ // already follow requested so just return the relationship
+ return p.AccountRelationshipGet(authed, form.TargetAccountID)
+ }
+
+ // make the follow request
+ fr := &gtsmodel.FollowRequest{
+ AccountID: authed.Account.ID,
+ TargetAccountID: form.TargetAccountID,
+ ShowReblogs: true,
+ URI: util.GenerateURIForFollow(authed.Account.Username, p.config.Protocol, p.config.Host),
+ Notify: false,
+ }
+ if form.Reblogs != nil {
+ fr.ShowReblogs = *form.Reblogs
+ }
+ if form.Notify != nil {
+ fr.Notify = *form.Notify
+ }
+
+ // whack it in the database
+ if err := p.db.Put(fr); err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("accountfollowcreate: error creating follow request in db: %s", err))
+ }
+
+ // if it's a local account that's not locked we can just straight up accept the follow request
+ if !targetAcct.Locked && targetAcct.Domain == "" {
+ if _, err := p.db.AcceptFollowRequest(authed.Account.ID, form.TargetAccountID); err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("accountfollowcreate: error accepting folow request for local unlocked account: %s", err))
+ }
+ // return the new relationship
+ return p.AccountRelationshipGet(authed, form.TargetAccountID)
+ }
+
+ // otherwise we leave the follow request as it is and we handle the rest of the process asynchronously
+ p.fromClientAPI <- gtsmodel.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsFollow,
+ APActivityType: gtsmodel.ActivityStreamsCreate,
+ GTSModel: &gtsmodel.Follow{
+ AccountID: authed.Account.ID,
+ TargetAccountID: form.TargetAccountID,
+ URI: fr.URI,
+ },
+ OriginAccount: authed.Account,
+ TargetAccount: targetAcct,
+ }
+
+ // return whatever relationship results from this
+ return p.AccountRelationshipGet(authed, form.TargetAccountID)
+}
+
+func (p *processor) AccountFollowRemove(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode) {
+ // if there's a block between the accounts we shouldn't do anything
+ blocked, err := p.db.Blocked(authed.Account.ID, targetAccountID)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+ if blocked {
+ return nil, NewErrorNotFound(fmt.Errorf("AccountFollowRemove: block exists between accounts"))
+ }
+
+ // make sure the target account actually exists in our db
+ targetAcct := &gtsmodel.Account{}
+ if err := p.db.GetByID(targetAccountID, targetAcct); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ return nil, NewErrorNotFound(fmt.Errorf("AccountFollowRemove: account %s not found in the db: %s", targetAccountID, err))
+ }
+ }
+
+ // check if a follow request exists, and remove it if it does (storing the URI for later)
+ var frChanged bool
+ var frURI string
+ fr := &gtsmodel.FollowRequest{}
+ if err := p.db.GetWhere([]db.Where{
+ {Key: "account_id", Value: authed.Account.ID},
+ {Key: "target_account_id", Value: targetAccountID},
+ }, fr); err == nil {
+ frURI = fr.URI
+ if err := p.db.DeleteByID(fr.ID, fr); err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow request from db: %s", err))
+ }
+ frChanged = true
+ }
+
+ // now do the same thing for any existing follow
+ var fChanged bool
+ var fURI string
+ f := &gtsmodel.Follow{}
+ if err := p.db.GetWhere([]db.Where{
+ {Key: "account_id", Value: authed.Account.ID},
+ {Key: "target_account_id", Value: targetAccountID},
+ }, f); err == nil {
+ fURI = f.URI
+ if err := p.db.DeleteByID(f.ID, f); err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("AccountFollowRemove: error removing follow from db: %s", err))
+ }
+ fChanged = true
+ }
+
+ // follow request status changed so send the UNDO activity to the channel for async processing
+ if frChanged {
+ p.fromClientAPI <- gtsmodel.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsFollow,
+ APActivityType: gtsmodel.ActivityStreamsUndo,
+ GTSModel: &gtsmodel.Follow{
+ AccountID: authed.Account.ID,
+ TargetAccountID: targetAccountID,
+ URI: frURI,
+ },
+ OriginAccount: authed.Account,
+ TargetAccount: targetAcct,
+ }
+ }
+
+ // follow status changed so send the UNDO activity to the channel for async processing
+ if fChanged {
+ p.fromClientAPI <- gtsmodel.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsFollow,
+ APActivityType: gtsmodel.ActivityStreamsUndo,
+ GTSModel: &gtsmodel.Follow{
+ AccountID: authed.Account.ID,
+ TargetAccountID: targetAccountID,
+ URI: fURI,
+ },
+ OriginAccount: authed.Account,
+ TargetAccount: targetAcct,
+ }
+ }
+
+ // return whatever relationship results from all this
+ return p.AccountRelationshipGet(authed, targetAccountID)
+}
diff --git a/internal/message/fediprocess.go b/internal/message/fediprocess.go
index 3c7c30e27..eb6e8b6d6 100644
--- a/internal/message/fediprocess.go
+++ b/internal/message/fediprocess.go
@@ -22,6 +22,7 @@ import (
"context"
"fmt"
"net/http"
+ "net/url"
"github.com/go-fed/activity/streams"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
@@ -46,7 +47,7 @@ func (p *processor) authenticateAndDereferenceFediRequest(username string, r *ht
// we might already have an entry for this account so check that first
requestingAccount := &gtsmodel.Account{}
- err = p.db.GetWhere("uri", requestingAccountURI.String(), requestingAccount)
+ err = p.db.GetWhere([]db.Where{{Key: "uri", Value: requestingAccountURI.String()}}, requestingAccount)
if err == nil {
// we do have it yay, return it
return requestingAccount, nil
@@ -122,6 +123,89 @@ func (p *processor) GetFediUser(requestedUsername string, request *http.Request)
return data, nil
}
+func (p *processor) GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) {
+ // get the account the request is referring to
+ requestedAccount := &gtsmodel.Account{}
+ if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
+ return nil, NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err))
+ }
+
+ // authenticate the request
+ requestingAccount, err := p.authenticateAndDereferenceFediRequest(requestedUsername, request)
+ if err != nil {
+ return nil, NewErrorNotAuthorized(err)
+ }
+
+ blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ if blocked {
+ return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
+ }
+
+ requestedAccountURI, err := url.Parse(requestedAccount.URI)
+ if err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", requestedAccount.URI, err))
+ }
+
+ requestedFollowers, err := p.federator.FederatingDB().Followers(context.Background(), requestedAccountURI)
+ if err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("error fetching followers for uri %s: %s", requestedAccountURI.String(), err))
+ }
+
+ data, err := streams.Serialize(requestedFollowers)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ return data, nil
+}
+
+func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, ErrorWithCode) {
+ // get the account the request is referring to
+ requestedAccount := &gtsmodel.Account{}
+ if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
+ return nil, NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err))
+ }
+
+ // authenticate the request
+ requestingAccount, err := p.authenticateAndDereferenceFediRequest(requestedUsername, request)
+ if err != nil {
+ return nil, NewErrorNotAuthorized(err)
+ }
+
+ blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ if blocked {
+ return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
+ }
+
+ s := &gtsmodel.Status{}
+ if err := p.db.GetWhere([]db.Where{
+ {Key: "id", Value: requestedStatusID},
+ {Key: "account_id", Value: requestedAccount.ID},
+ }, s); err != nil {
+ return nil, NewErrorNotFound(fmt.Errorf("database error getting status with id %s and account id %s: %s", requestedStatusID, requestedAccount.ID, err))
+ }
+
+ asStatus, err := p.tc.StatusToAS(s)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ data, err := streams.Serialize(asStatus)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ return data, nil
+}
+
func (p *processor) GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WebfingerAccountResponse, ErrorWithCode) {
// get the account the request is referring to
requestedAccount := &gtsmodel.Account{}
diff --git a/internal/message/fromclientapiprocess.go b/internal/message/fromclientapiprocess.go
index 1a12216e7..e91bd6ce4 100644
--- a/internal/message/fromclientapiprocess.go
+++ b/internal/message/fromclientapiprocess.go
@@ -19,55 +19,198 @@
package message
import (
+ "context"
"errors"
"fmt"
+ "net/url"
+ "github.com/go-fed/activity/streams"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error {
- switch clientMsg.APObjectType {
- case gtsmodel.ActivityStreamsNote:
- status, ok := clientMsg.GTSModel.(*gtsmodel.Status)
- if !ok {
- return errors.New("note was not parseable as *gtsmodel.Status")
- }
+ switch clientMsg.APActivityType {
+ case gtsmodel.ActivityStreamsCreate:
+ // CREATE
+ switch clientMsg.APObjectType {
+ case gtsmodel.ActivityStreamsNote:
+ // CREATE NOTE
+ status, ok := clientMsg.GTSModel.(*gtsmodel.Status)
+ if !ok {
+ return errors.New("note was not parseable as *gtsmodel.Status")
+ }
- if err := p.notifyStatus(status); err != nil {
- return err
- }
+ if err := p.notifyStatus(status); err != nil {
+ return err
+ }
+
+ if status.VisibilityAdvanced.Federated {
+ return p.federateStatus(status)
+ }
+ return nil
+ case gtsmodel.ActivityStreamsFollow:
+ // CREATE FOLLOW (request)
+ follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow)
+ if !ok {
+ return errors.New("follow was not parseable as *gtsmodel.Follow")
+ }
+
+ if err := p.notifyFollow(follow); err != nil {
+ return err
+ }
- if status.VisibilityAdvanced.Federated {
- return p.federateStatus(status)
+ return p.federateFollow(follow, clientMsg.OriginAccount, clientMsg.TargetAccount)
+ }
+ case gtsmodel.ActivityStreamsUpdate:
+ // UPDATE
+ case gtsmodel.ActivityStreamsAccept:
+ // ACCEPT
+ switch clientMsg.APObjectType {
+ case gtsmodel.ActivityStreamsFollow:
+ // ACCEPT FOLLOW
+ follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow)
+ if !ok {
+ return errors.New("accept was not parseable as *gtsmodel.Follow")
+ }
+ return p.federateAcceptFollowRequest(follow, clientMsg.OriginAccount, clientMsg.TargetAccount)
+ }
+ case gtsmodel.ActivityStreamsUndo:
+ // UNDO
+ switch clientMsg.APObjectType {
+ case gtsmodel.ActivityStreamsFollow:
+ // UNDO FOLLOW
+ follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow)
+ if !ok {
+ return errors.New("undo was not parseable as *gtsmodel.Follow")
+ }
+ return p.federateUnfollow(follow, clientMsg.OriginAccount, clientMsg.TargetAccount)
}
- return nil
}
- return fmt.Errorf("message type unprocessable: %+v", clientMsg)
+ return nil
}
func (p *processor) federateStatus(status *gtsmodel.Status) error {
- // // derive the sending account -- it might be attached to the status already
- // sendingAcct := &gtsmodel.Account{}
- // if status.GTSAccount != nil {
- // sendingAcct = status.GTSAccount
- // } else {
- // // it wasn't attached so get it from the db instead
- // if err := p.db.GetByID(status.AccountID, sendingAcct); err != nil {
- // return err
- // }
- // }
-
- // outboxURI, err := url.Parse(sendingAcct.OutboxURI)
- // if err != nil {
- // return err
- // }
-
- // // convert the status to AS format Note
- // note, err := p.tc.StatusToAS(status)
- // if err != nil {
- // return err
- // }
-
- // _, err = p.federator.FederatingActor().Send(context.Background(), outboxURI, note)
- return nil
+ asStatus, err := p.tc.StatusToAS(status)
+ if err != nil {
+ return fmt.Errorf("federateStatus: error converting status to as format: %s", err)
+ }
+
+ outboxIRI, err := url.Parse(status.GTSAccount.OutboxURI)
+ if err != nil {
+ return fmt.Errorf("federateStatus: error parsing outboxURI %s: %s", status.GTSAccount.OutboxURI, err)
+ }
+
+ _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, asStatus)
+ return err
+}
+
+func (p *processor) federateFollow(follow *gtsmodel.Follow, 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
+ }
+
+ asFollow, err := p.tc.FollowToAS(follow, originAccount, targetAccount)
+ if err != nil {
+ return fmt.Errorf("federateFollow: error converting follow to as format: %s", err)
+ }
+
+ outboxIRI, err := url.Parse(originAccount.OutboxURI)
+ if err != nil {
+ return fmt.Errorf("federateFollow: error parsing outboxURI %s: %s", originAccount.OutboxURI, err)
+ }
+
+ _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, asFollow)
+ return err
+}
+
+func (p *processor) federateUnfollow(follow *gtsmodel.Follow, 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
+ }
+
+ // recreate the follow
+ asFollow, err := p.tc.FollowToAS(follow, originAccount, targetAccount)
+ if err != nil {
+ return fmt.Errorf("federateUnfollow: error converting follow to as format: %s", err)
+ }
+
+ targetAccountURI, err := url.Parse(targetAccount.URI)
+ if err != nil {
+ return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err)
+ }
+
+ // create an Undo and set the appropriate actor on it
+ undo := streams.NewActivityStreamsUndo()
+ undo.SetActivityStreamsActor(asFollow.GetActivityStreamsActor())
+
+ // Set the recreated follow as the 'object' property.
+ undoObject := streams.NewActivityStreamsObjectProperty()
+ undoObject.AppendActivityStreamsFollow(asFollow)
+ undo.SetActivityStreamsObject(undoObject)
+
+ // Set the To of the undo as the target of the recreated follow
+ undoTo := streams.NewActivityStreamsToProperty()
+ undoTo.AppendIRI(targetAccountURI)
+ undo.SetActivityStreamsTo(undoTo)
+
+ outboxIRI, err := url.Parse(originAccount.OutboxURI)
+ if err != nil {
+ return fmt.Errorf("federateUnfollow: error parsing outboxURI %s: %s", originAccount.OutboxURI, err)
+ }
+
+ // send off the Undo
+ _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, undo)
+ return err
+}
+
+func (p *processor) federateAcceptFollowRequest(follow *gtsmodel.Follow, 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
+ }
+
+ // recreate the AS follow
+ asFollow, err := p.tc.FollowToAS(follow, originAccount, targetAccount)
+ if err != nil {
+ return fmt.Errorf("federateUnfollow: error converting follow to as format: %s", err)
+ }
+
+ acceptingAccountURI, err := url.Parse(targetAccount.URI)
+ if err != nil {
+ return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err)
+ }
+
+ requestingAccountURI, err := url.Parse(originAccount.URI)
+ if err != nil {
+ return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err)
+ }
+
+ // create an Accept
+ accept := streams.NewActivityStreamsAccept()
+
+ // set the accepting actor on it
+ acceptActorProp := streams.NewActivityStreamsActorProperty()
+ acceptActorProp.AppendIRI(acceptingAccountURI)
+ accept.SetActivityStreamsActor(acceptActorProp)
+
+ // Set the recreated follow as the 'object' property.
+ acceptObject := streams.NewActivityStreamsObjectProperty()
+ acceptObject.AppendActivityStreamsFollow(asFollow)
+ accept.SetActivityStreamsObject(acceptObject)
+
+ // Set the To of the accept as the originator of the follow
+ acceptTo := streams.NewActivityStreamsToProperty()
+ acceptTo.AppendIRI(requestingAccountURI)
+ accept.SetActivityStreamsTo(acceptTo)
+
+ outboxIRI, err := url.Parse(targetAccount.OutboxURI)
+ if err != nil {
+ return fmt.Errorf("federateAcceptFollowRequest: error parsing outboxURI %s: %s", originAccount.OutboxURI, err)
+ }
+
+ // send off the accept using the accepter's outbox
+ _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, accept)
+ return err
}
diff --git a/internal/message/fromcommonprocess.go b/internal/message/fromcommonprocess.go
index 14f145df9..d557b7962 100644
--- a/internal/message/fromcommonprocess.go
+++ b/internal/message/fromcommonprocess.go
@@ -23,3 +23,7 @@ import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
func (p *processor) notifyStatus(status *gtsmodel.Status) error {
return nil
}
+
+func (p *processor) notifyFollow(follow *gtsmodel.Follow) error {
+ return nil
+}
diff --git a/internal/message/fromfederatorprocess.go b/internal/message/fromfederatorprocess.go
index 2dd8e9e3b..ffaa1b93b 100644
--- a/internal/message/fromfederatorprocess.go
+++ b/internal/message/fromfederatorprocess.go
@@ -38,24 +38,60 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er
l.Debug("entering function PROCESS FROM FEDERATOR")
- switch federatorMsg.APObjectType {
- case gtsmodel.ActivityStreamsNote:
+ switch federatorMsg.APActivityType {
+ case gtsmodel.ActivityStreamsCreate:
+ // CREATE
+ switch federatorMsg.APObjectType {
+ case gtsmodel.ActivityStreamsNote:
+ // CREATE A STATUS
+ incomingStatus, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
+ if !ok {
+ return errors.New("note was not parseable as *gtsmodel.Status")
+ }
- incomingStatus, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
- if !ok {
- return errors.New("note was not parseable as *gtsmodel.Status")
- }
+ l.Debug("will now derefence incoming status")
+ if err := p.dereferenceStatusFields(incomingStatus); err != nil {
+ return fmt.Errorf("error dereferencing status from federator: %s", err)
+ }
+ if err := p.db.UpdateByID(incomingStatus.ID, incomingStatus); err != nil {
+ return fmt.Errorf("error updating dereferenced status in the db: %s", err)
+ }
- l.Debug("will now derefence incoming status")
- if err := p.dereferenceStatusFields(incomingStatus); err != nil {
- return fmt.Errorf("error dereferencing status from federator: %s", err)
- }
- if err := p.db.UpdateByID(incomingStatus.ID, incomingStatus); err != nil {
- return fmt.Errorf("error updating dereferenced status in the db: %s", err)
+ if err := p.notifyStatus(incomingStatus); err != nil {
+ return err
+ }
+ case gtsmodel.ActivityStreamsProfile:
+ // CREATE AN ACCOUNT
+ incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account)
+ if !ok {
+ return errors.New("profile was not parseable as *gtsmodel.Account")
+ }
+
+ l.Debug("will now derefence incoming account")
+ if err := p.dereferenceAccountFields(incomingAccount, ""); err != nil {
+ return fmt.Errorf("error dereferencing account from federator: %s", err)
+ }
+ if err := p.db.UpdateByID(incomingAccount.ID, incomingAccount); err != nil {
+ return fmt.Errorf("error updating dereferenced account in the db: %s", err)
+ }
}
+ case gtsmodel.ActivityStreamsUpdate:
+ // UPDATE
+ switch federatorMsg.APObjectType {
+ case gtsmodel.ActivityStreamsProfile:
+ // UPDATE AN ACCOUNT
+ incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account)
+ if !ok {
+ return errors.New("profile was not parseable as *gtsmodel.Account")
+ }
- if err := p.notifyStatus(incomingStatus); err != nil {
- return err
+ l.Debug("will now derefence incoming account")
+ if err := p.dereferenceAccountFields(incomingAccount, ""); err != nil {
+ return fmt.Errorf("error dereferencing account from federator: %s", err)
+ }
+ if err := p.db.UpdateByID(incomingAccount.ID, incomingAccount); err != nil {
+ return fmt.Errorf("error updating dereferenced account in the db: %s", err)
+ }
}
}
@@ -121,7 +157,7 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
// it might have been processed elsewhere so check first if it's already in the database or not
maybeAttachment := &gtsmodel.MediaAttachment{}
- err := p.db.GetWhere("remote_url", a.RemoteURL, maybeAttachment)
+ err := p.db.GetWhere([]db.Where{{Key: "remote_url", Value: a.RemoteURL}}, maybeAttachment)
if err == nil {
// we already have it in the db, dereferenced, no need to do it again
l.Debugf("attachment already exists with id %s", maybeAttachment.ID)
@@ -170,7 +206,7 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
m.OriginAccountURI = status.GTSAccount.URI
targetAccount := &gtsmodel.Account{}
- if err := p.db.GetWhere("uri", uri.String(), targetAccount); err != nil {
+ if err := p.db.GetWhere([]db.Where{{Key: "uri", Value: uri.String()}}, targetAccount); err != nil {
// proper error
if _, ok := err.(db.ErrNoEntries); !ok {
return fmt.Errorf("db error checking for account with uri %s", uri.String())
@@ -206,3 +242,27 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
return nil
}
+
+func (p *processor) dereferenceAccountFields(account *gtsmodel.Account, requestingUsername string) error {
+ l := p.log.WithFields(logrus.Fields{
+ "func": "dereferenceAccountFields",
+ "requestingUsername": requestingUsername,
+ })
+
+ t, err := p.federator.GetTransportForUser(requestingUsername)
+ if err != nil {
+ return fmt.Errorf("error getting transport for user: %s", err)
+ }
+
+ // fetch the header and avatar
+ if err := p.fetchHeaderAndAviForAccount(account, t); err != nil {
+ // if this doesn't work, just skip it -- we can do it later
+ l.Debugf("error fetching header/avi for account: %s", err)
+ }
+
+ if err := p.db.UpdateByID(account.ID, account); err != nil {
+ return fmt.Errorf("error updating account in database: %s", err)
+ }
+
+ return nil
+}
diff --git a/internal/message/frprocess.go b/internal/message/frprocess.go
index cc3838598..fd64b4c50 100644
--- a/internal/message/frprocess.go
+++ b/internal/message/frprocess.go
@@ -48,11 +48,28 @@ func (p *processor) FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, Err
return accts, nil
}
-func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) ErrorWithCode {
- if err := p.db.AcceptFollowRequest(accountID, auth.Account.ID); err != nil {
- return NewErrorNotFound(err)
+func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) (*apimodel.Relationship, ErrorWithCode) {
+ follow, err := p.db.AcceptFollowRequest(accountID, auth.Account.ID)
+ if err != nil {
+ return nil, NewErrorNotFound(err)
}
- return nil
+
+ p.fromClientAPI <- gtsmodel.FromClientAPI{
+ APActivityType: gtsmodel.ActivityStreamsAccept,
+ GTSModel: follow,
+ }
+
+ gtsR, err := p.db.GetRelationship(auth.Account.ID, accountID)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ r, err := p.tc.RelationshipToMasto(gtsR)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ return r, nil
}
func (p *processor) FollowRequestDeny(auth *oauth.Auth) ErrorWithCode {
diff --git a/internal/message/instanceprocess.go b/internal/message/instanceprocess.go
index 05ea103fd..f544fbf30 100644
--- a/internal/message/instanceprocess.go
+++ b/internal/message/instanceprocess.go
@@ -22,12 +22,13 @@ import (
"fmt"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) InstanceGet(domain string) (*apimodel.Instance, ErrorWithCode) {
i := &gtsmodel.Instance{}
- if err := p.db.GetWhere("domain", domain, i); err != nil {
+ if err := p.db.GetWhere([]db.Where{{Key: "domain", Value: domain}}, i); err != nil {
return nil, NewErrorInternalError(fmt.Errorf("db error fetching instance %s: %s", p.config.Host, err))
}
diff --git a/internal/message/processor.go b/internal/message/processor.go
index c9ba5f858..e9888d647 100644
--- a/internal/message/processor.go
+++ b/internal/message/processor.go
@@ -71,8 +71,16 @@ type Processor interface {
// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
// the account given in authed.
AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, ErrorWithCode)
- // AccountFollowersGet
+ // AccountFollowersGet fetches a list of the target account's followers.
AccountFollowersGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode)
+ // AccountFollowingGet fetches a list of the accounts that target account is following.
+ AccountFollowingGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode)
+ // AccountRelationshipGet returns a relationship model describing the relationship of the targetAccount to the Authed account.
+ AccountRelationshipGet(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode)
+ // AccountFollowCreate handles a follow request to an account, either remote or local.
+ AccountFollowCreate(authed *oauth.Auth, form *apimodel.AccountFollowRequest) (*apimodel.Relationship, ErrorWithCode)
+ // AccountFollowRemove handles the removal of a follow/follow request to an account, either remote or local.
+ AccountFollowRemove(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode)
// AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form.
AdminEmojiCreate(authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error)
@@ -86,7 +94,7 @@ type Processor interface {
// FollowRequestsGet handles the getting of the authed account's incoming follow requests
FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, ErrorWithCode)
// FollowRequestAccept handles the acceptance of a follow request from the given account ID
- FollowRequestAccept(auth *oauth.Auth, accountID string) ErrorWithCode
+ FollowRequestAccept(auth *oauth.Auth, accountID string) (*apimodel.Relationship, ErrorWithCode)
// InstanceGet retrieves instance information for serving at api/v1/instance
InstanceGet(domain string) (*apimodel.Instance, ErrorWithCode)
@@ -125,6 +133,14 @@ type Processor interface {
// before returning a JSON serializable interface to the caller.
GetFediUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode)
+ // GetFediFollowers handles the getting of a fedi/activitypub representation of a user/account's followers, performing appropriate
+ // authentication before returning a JSON serializable interface to the caller.
+ GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode)
+
+ // GetFediStatus handles the getting of a fedi/activitypub representation of a particular status, performing appropriate
+ // authentication before returning a JSON serializable interface to the caller.
+ GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, ErrorWithCode)
+
// GetWebfingerAccount handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups.
GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WebfingerAccountResponse, ErrorWithCode)
@@ -200,15 +216,11 @@ func (p *processor) Start() error {
DistLoop:
for {
select {
- // case clientMsg := <-p.toClientAPI:
- // p.log.Infof("received message TO client API: %+v", clientMsg)
case clientMsg := <-p.fromClientAPI:
p.log.Infof("received message FROM client API: %+v", clientMsg)
if err := p.processFromClientAPI(clientMsg); err != nil {
p.log.Error(err)
}
- // case federatorMsg := <-p.toFederator:
- // p.log.Infof("received message TO federator: %+v", federatorMsg)
case federatorMsg := <-p.fromFederator:
p.log.Infof("received message FROM federator: %+v", federatorMsg)
if err := p.processFromFederator(federatorMsg); err != nil {
diff --git a/internal/message/processorutil.go b/internal/message/processorutil.go
index 676635a51..67c96abe0 100644
--- a/internal/message/processorutil.go
+++ b/internal/message/processorutil.go
@@ -29,6 +29,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/media"
+ "github.com/superseriousbusiness/gotosocial/internal/transport"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
@@ -205,7 +206,7 @@ func (p *processor) processMentions(form *apimodel.AdvancedStatusCreateForm, acc
if err := p.db.Put(menchie); err != nil {
return fmt.Errorf("error putting mentions in db: %s", err)
}
- menchies = append(menchies, menchie.TargetAccountID)
+ menchies = append(menchies, menchie.ID)
}
// add full populated gts menchies to the status for passing them around conveniently
status.GTSMentions = gtsMenchies
@@ -280,7 +281,7 @@ func (p *processor) updateAccountAvatar(avatar *multipart.FileHeader, accountID
}
// do the setting
- avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Avatar)
+ avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Avatar, "")
if err != nil {
return nil, fmt.Errorf("error processing avatar: %s", err)
}
@@ -313,10 +314,42 @@ func (p *processor) updateAccountHeader(header *multipart.FileHeader, accountID
}
// do the setting
- headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Header)
+ headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Header, "")
if err != nil {
return nil, fmt.Errorf("error processing header: %s", err)
}
return headerInfo, f.Close()
}
+
+// fetchHeaderAndAviForAccount fetches the header and avatar for a remote account, using a transport
+// on behalf of requestingUsername.
+//
+// targetAccount's AvatarMediaAttachmentID and HeaderMediaAttachmentID will be updated as necessary.
+//
+// SIDE EFFECTS: remote header and avatar will be stored in local storage, and the database will be updated
+// to reflect the creation of these new attachments.
+func (p *processor) fetchHeaderAndAviForAccount(targetAccount *gtsmodel.Account, t transport.Transport) error {
+ if targetAccount.AvatarRemoteURL != "" && targetAccount.AvatarMediaAttachmentID == "" {
+ a, err := p.mediaHandler.ProcessRemoteHeaderOrAvatar(t, &gtsmodel.MediaAttachment{
+ RemoteURL: targetAccount.AvatarRemoteURL,
+ Avatar: true,
+ }, targetAccount.ID)
+ if err != nil {
+ return fmt.Errorf("error processing avatar for user: %s", err)
+ }
+ targetAccount.AvatarMediaAttachmentID = a.ID
+ }
+
+ if targetAccount.HeaderRemoteURL != "" && targetAccount.HeaderMediaAttachmentID == "" {
+ a, err := p.mediaHandler.ProcessRemoteHeaderOrAvatar(t, &gtsmodel.MediaAttachment{
+ RemoteURL: targetAccount.HeaderRemoteURL,
+ Header: true,
+ }, targetAccount.ID)
+ if err != nil {
+ return fmt.Errorf("error processing header for user: %s", err)
+ }
+ targetAccount.HeaderMediaAttachmentID = a.ID
+ }
+ return nil
+}