diff options
Diffstat (limited to 'internal/message')
-rw-r--r-- | internal/message/accountprocess.go | 250 | ||||
-rw-r--r-- | internal/message/fediprocess.go | 86 | ||||
-rw-r--r-- | internal/message/fromclientapiprocess.go | 217 | ||||
-rw-r--r-- | internal/message/fromcommonprocess.go | 4 | ||||
-rw-r--r-- | internal/message/fromfederatorprocess.go | 92 | ||||
-rw-r--r-- | internal/message/frprocess.go | 25 | ||||
-rw-r--r-- | internal/message/instanceprocess.go | 3 | ||||
-rw-r--r-- | internal/message/processor.go | 24 | ||||
-rw-r--r-- | internal/message/processorutil.go | 39 |
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 := >smodel.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 := >smodel.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 := >smodel.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: >smodel.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 := >smodel.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 := >smodel.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 := >smodel.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: >smodel.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: >smodel.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 := >smodel.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 := >smodel.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 := >smodel.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 := >smodel.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 := >smodel.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 := >smodel.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 := >smodel.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 := >smodel.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 := >smodel.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, >smodel.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, >smodel.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 +} |