diff --git a/internal/message/fromclientapiprocess.go b/internal/message/fromclientapiprocess.go index 27e533c7f..f97c9934b 100644 --- a/internal/message/fromclientapiprocess.go +++ b/internal/message/fromclientapiprocess.go @@ -24,7 +24,6 @@ import ( "fmt" "net/url" - "github.com/go-fed/activity/pub" "github.com/go-fed/activity/streams" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -66,16 +65,20 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error // UPDATE case gtsmodel.ActivityStreamsAccept: // ACCEPT - follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) - if !ok { - return errors.New("accept was not parseable as *gtsmodel.Follow") + 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) } - return p.federateAcceptFollowRequest(follow) case gtsmodel.ActivityStreamsUndo: // UNDO switch clientMsg.APObjectType { - // UNDO FOLLOW case gtsmodel.ActivityStreamsFollow: + // UNDO FOLLOW follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) if !ok { return errors.New("undo was not parseable as *gtsmodel.Follow") @@ -114,6 +117,11 @@ func (p *processor) federateStatus(status *gtsmodel.Status) error { } 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) @@ -129,6 +137,11 @@ func (p *processor) federateFollow(follow *gtsmodel.Follow, originAccount *gtsmo } 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 { @@ -164,75 +177,52 @@ func (p *processor) federateUnfollow(follow *gtsmodel.Follow, originAccount *gts return err } -func (p *processor) federateAcceptFollowRequest(follow *gtsmodel.Follow) error { - - // TODO: tidy up this whole function -- move most of the logic for the conversion to the type converter because this is just a mess! Shame on me! - - followAccepter := >smodel.Account{} - if err := p.db.GetByID(follow.TargetAccountID, followAccepter); err != nil { - return fmt.Errorf("error federating follow accept: %s", 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 } - followAccepterIRI, err := url.Parse(followAccepter.URI) + + // recreate the AS follow + asFollow, err := p.tc.FollowToAS(follow, originAccount, targetAccount) if err != nil { - return fmt.Errorf("error parsing URL: %s", err) + return fmt.Errorf("federateUnfollow: error converting follow to as format: %s", err) } - followAccepterOutboxIRI, err := url.Parse(followAccepter.OutboxURI) + + acceptingAccountURI, err := url.Parse(targetAccount.URI) if err != nil { - return fmt.Errorf("error parsing URL: %s", err) + return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err) } - me := streams.NewActivityStreamsActorProperty() - me.AppendIRI(followAccepterIRI) - followRequester := >smodel.Account{} - if err := p.db.GetByID(follow.AccountID, followRequester); err != nil { - return fmt.Errorf("error federating follow accept: %s", err) - } - requesterIRI, err := url.Parse(followRequester.URI) + requestingAccountURI, err := url.Parse(originAccount.URI) if err != nil { - return fmt.Errorf("error parsing URL: %s", err) + return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err) } - them := streams.NewActivityStreamsActorProperty() - them.AppendIRI(requesterIRI) - // prepare the follow - ASFollow := streams.NewActivityStreamsFollow() - // set the follow requester as the actor - ASFollow.SetActivityStreamsActor(them) - // set the ID from the follow - ASFollowURI, err := url.Parse(follow.URI) + // 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("error parsing URL: %s", err) + return fmt.Errorf("federateAcceptFollowRequest: error parsing outboxURI %s: %s", originAccount.OutboxURI, err) } - ASFollowIDProp := streams.NewJSONLDIdProperty() - ASFollowIDProp.SetIRI(ASFollowURI) - ASFollow.SetJSONLDId(ASFollowIDProp) - // set the object as the accepter URI - ASFollowObjectProp := streams.NewActivityStreamsObjectProperty() - ASFollowObjectProp.AppendIRI(followAccepterIRI) - - // Prepare the response. - ASAccept := streams.NewActivityStreamsAccept() - // Set us as the 'actor'. - ASAccept.SetActivityStreamsActor(me) - - // Set the Follow as the 'object' property. - ASAcceptObject := streams.NewActivityStreamsObjectProperty() - ASAcceptObject.AppendActivityStreamsFollow(ASFollow) - ASAccept.SetActivityStreamsObject(ASAcceptObject) - - // Add all actors on the original Follow to the 'to' property. - ASAcceptTo := streams.NewActivityStreamsToProperty() - followActors := ASFollow.GetActivityStreamsActor() - for iter := followActors.Begin(); iter != followActors.End(); iter = iter.Next() { - id, err := pub.ToId(iter) - if err != nil { - return err - } - ASAcceptTo.AppendIRI(id) - } - ASAccept.SetActivityStreamsTo(ASAcceptTo) - - _, err = p.federator.FederatingActor().Send(context.Background(), followAccepterOutboxIRI, ASAccept) + // send off the accept using the accepter's outbox + _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, accept) return err } diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index 58814c55d..aad5b3e2e 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -26,6 +26,10 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) +const ( + asPublicURI = "https://www.w3.org/ns/activitystreams#Public" +) + // TypeConverter is an interface for the common action of converting between apimodule (frontend, serializable) models, // internal gts models used in the database, and activitypub models used in federation. // @@ -112,6 +116,8 @@ type TypeConverter interface { // FollowToASFollow converts a gts model Follow into an activity streams Follow, suitable for federation FollowToAS(f *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (vocab.ActivityStreamsFollow, error) + + MentionToAS(m *gtsmodel.Mention) (vocab.ActivityStreamsMention, error) } type converter struct { diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index 37da25f1e..fb8d60d7c 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -257,6 +257,134 @@ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerso } func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) { + // ensure prerequisites here before we get stuck in + + // check if author account is already attached to status and attach it if not + // if we can't retrieve this, bail here already because we can't attribute the status to anyone + if s.GTSAccount == nil { + a := >smodel.Account{} + if err := c.db.GetByID(s.AccountID, a); err != nil { + return nil, fmt.Errorf("StatusToAS: error retrieving author account from db: %s", err) + } + s.GTSAccount = a + } + + // create the Note! + status := streams.NewActivityStreamsNote() + + // id + statusURI, err := url.Parse(s.URI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.URI, err) + } + statusIDProp := streams.NewJSONLDIdProperty() + statusIDProp.SetIRI(statusURI) + status.SetJSONLDId(statusIDProp) + + // type + // will be set automatically by go-fed + + // summary aka cw + statusSummaryProp := streams.NewActivityStreamsSummaryProperty() + statusSummaryProp.AppendXMLSchemaString(s.ContentWarning) + status.SetActivityStreamsSummary(statusSummaryProp) + + // inReplyTo + if s.InReplyToID != "" { + // fetch the replied status if we don't have it on hand already + if s.GTSReplyToStatus == nil { + rs := >smodel.Status{} + if err := c.db.GetByID(s.InReplyToID, rs); err != nil { + return nil, fmt.Errorf("StatusToAS: error retrieving replied-to status from db: %s", err) + } + s.GTSReplyToStatus = rs + } + rURI, err := url.Parse(s.GTSReplyToStatus.URI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSReplyToStatus.URI, err) + } + + inReplyToProp := streams.NewActivityStreamsInReplyToProperty() + inReplyToProp.AppendIRI(rURI) + status.SetActivityStreamsInReplyTo(inReplyToProp) + } + + // published + publishedProp := streams.NewActivityStreamsPublishedProperty() + publishedProp.Set(s.CreatedAt) + status.SetActivityStreamsPublished(publishedProp) + + // url + if s.URL != "" { + sURL, err := url.Parse(s.URL) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.URL, err) + } + + urlProp := streams.NewActivityStreamsUrlProperty() + urlProp.AppendIRI(sURL) + status.SetActivityStreamsUrl(urlProp) + } + + // attributedTo + authorAccountURI, err := url.Parse(s.GTSAccount.URI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSAccount.URI, err) + } + attributedToProp := streams.NewActivityStreamsAttributedToProperty() + attributedToProp.AppendIRI(authorAccountURI) + status.SetActivityStreamsAttributedTo(attributedToProp) + + // tags + tagProp := streams.NewActivityStreamsTagProperty() + + // tag -- mentions + for _, m := range s.GTSMentions { + asMention, err := c.MentionToAS(m) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error converting mention to AS mention: %s", err) + } + tagProp.AppendActivityStreamsMention(asMention) + } + + // tag -- emojis + + // tag -- hashtags + + + status.SetActivityStreamsTag(tagProp) + + // parse out some URIs we need here + authorFollowersURI, err := url.Parse(s.GTSAccount.FollowersURI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSAccount.FollowersURI, err) + } + + publicURI, err := url.Parse(asPublicURI) + if err != nil { + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", asPublicURI, err) + } + + // to + switch s.Visibility { + case gtsmodel.VisibilityDirect: + } + + // cc + + // conversation + + // content + + // attachment + + + + + + // replies + + return nil, nil } @@ -305,3 +433,13 @@ func (c *converter) FollowToAS(f *gtsmodel.Follow, originAccount *gtsmodel.Accou return follow, nil } + +func (c *converter) MentionToAS(m *gtsmodel.Mention) (vocab.ActivityStreamsMention, error) { + mention := streams.NewActivityStreamsMention() + + if m.NameString == "" { + m.NameString = + } + + +}