mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-02 13:22:24 -06:00
Handle forwarded messages (#273)
* correct path of foss_satan * add APIri and notes * test create forward note * rename target => receiving account * split up create into separate funcs * update extractFromCtx * tidy up from federator processing * foss satan => http not https * check if status in db * mock dereference of status from IRI * add forward message deref test * update test with activities * add remote_account_2 to test rig
This commit is contained in:
parent
3dc7644ae6
commit
367bdca250
18 changed files with 765 additions and 383 deletions
|
|
@ -22,11 +22,13 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
)
|
||||
|
|
@ -59,144 +61,261 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
|||
l.Debug("entering Create")
|
||||
}
|
||||
|
||||
targetAcct, fromFederatorChan, err := extractFromCtx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if targetAcct == nil || fromFederatorChan == nil {
|
||||
// If the target account or federator channel wasn't set on the context, that means this request didn't pass
|
||||
receivingAccount, requestingAccount, fromFederatorChan := extractFromCtx(ctx)
|
||||
if receivingAccount == nil || fromFederatorChan == nil {
|
||||
// If the receiving account or federator channel wasn't set on the context, that means this request didn't pass
|
||||
// through the API, but came from inside GtS as the result of another activity on this instance. That being so,
|
||||
// we can safely just ignore this activity, since we know we've already processed it elsewhere.
|
||||
return nil
|
||||
}
|
||||
|
||||
switch asType.GetTypeName() {
|
||||
case ap.ActivityCreate:
|
||||
// CREATE SOMETHING
|
||||
create, ok := asType.(vocab.ActivityStreamsCreate)
|
||||
if !ok {
|
||||
return errors.New("CREATE: could not convert type to create")
|
||||
}
|
||||
object := create.GetActivityStreamsObject()
|
||||
for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
|
||||
switch objectIter.GetType().GetTypeName() {
|
||||
case ap.ObjectNote:
|
||||
// CREATE A NOTE
|
||||
note := objectIter.GetActivityStreamsNote()
|
||||
status, err := f.typeConverter.ASStatusToStatus(ctx, note)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CREATE: error converting note to status: %s", err)
|
||||
}
|
||||
|
||||
// id the status based on the time it was created
|
||||
statusID, err := id.NewULIDFromTime(status.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status.ID = statusID
|
||||
|
||||
if err := f.db.PutStatus(ctx, status); err != nil {
|
||||
if err == db.ErrAlreadyExists {
|
||||
// the status already exists in the database, which means we've already handled everything else,
|
||||
// so we can just return nil here and be done with it.
|
||||
return nil
|
||||
}
|
||||
// an actual error has happened
|
||||
return fmt.Errorf("CREATE: database error inserting status: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
ReceivingAccount: targetAcct,
|
||||
}
|
||||
}
|
||||
}
|
||||
case ap.ActivityFollow:
|
||||
// FOLLOW SOMETHING
|
||||
follow, ok := asType.(vocab.ActivityStreamsFollow)
|
||||
if !ok {
|
||||
return errors.New("CREATE: could not convert type to follow")
|
||||
}
|
||||
|
||||
followRequest, err := f.typeConverter.ASFollowToFollowRequest(ctx, follow)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CREATE: could not convert Follow to follow request: %s", err)
|
||||
}
|
||||
|
||||
newID, err := id.NewULID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
followRequest.ID = newID
|
||||
|
||||
if err := f.db.Put(ctx, followRequest); err != nil {
|
||||
return fmt.Errorf("CREATE: database error inserting follow request: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: followRequest,
|
||||
ReceivingAccount: targetAcct,
|
||||
}
|
||||
case ap.ActivityLike:
|
||||
// LIKE SOMETHING
|
||||
like, ok := asType.(vocab.ActivityStreamsLike)
|
||||
if !ok {
|
||||
return errors.New("CREATE: could not convert type to like")
|
||||
}
|
||||
|
||||
fave, err := f.typeConverter.ASLikeToFave(ctx, like)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CREATE: could not convert Like to fave: %s", err)
|
||||
}
|
||||
|
||||
newID, err := id.NewULID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fave.ID = newID
|
||||
|
||||
if err := f.db.Put(ctx, fave); err != nil {
|
||||
return fmt.Errorf("CREATE: database error inserting fave: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: fave,
|
||||
ReceivingAccount: targetAcct,
|
||||
}
|
||||
case ap.ActivityBlock:
|
||||
// BLOCK SOMETHING
|
||||
blockable, ok := asType.(vocab.ActivityStreamsBlock)
|
||||
if !ok {
|
||||
return errors.New("CREATE: could not convert type to block")
|
||||
}
|
||||
|
||||
block, err := f.typeConverter.ASBlockToBlock(ctx, blockable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CREATE: could not convert Block to gts model block")
|
||||
}
|
||||
|
||||
newID, err := id.NewULID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block.ID = newID
|
||||
|
||||
if err := f.db.Put(ctx, block); err != nil {
|
||||
return fmt.Errorf("CREATE: database error inserting block: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ActivityBlock,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: block,
|
||||
ReceivingAccount: targetAcct,
|
||||
}
|
||||
return f.activityBlock(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||
case ap.ActivityCreate:
|
||||
// CREATE SOMETHING
|
||||
return f.activityCreate(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||
case ap.ActivityFollow:
|
||||
// FOLLOW SOMETHING
|
||||
return f.activityFollow(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||
case ap.ActivityLike:
|
||||
// LIKE SOMETHING
|
||||
return f.activityLike(ctx, asType, receivingAccount, requestingAccount, fromFederatorChan)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
BLOCK HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, receiving *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) error {
|
||||
blockable, ok := asType.(vocab.ActivityStreamsBlock)
|
||||
if !ok {
|
||||
return errors.New("activityBlock: could not convert type to block")
|
||||
}
|
||||
|
||||
block, err := f.typeConverter.ASBlockToBlock(ctx, blockable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("activityBlock: could not convert Block to gts model block")
|
||||
}
|
||||
|
||||
newID, err := id.NewULID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block.ID = newID
|
||||
|
||||
if err := f.db.Put(ctx, block); err != nil {
|
||||
return fmt.Errorf("activityBlock: database error inserting block: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ActivityBlock,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: block,
|
||||
ReceivingAccount: receiving,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
CREATE HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityCreate(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) error {
|
||||
create, ok := asType.(vocab.ActivityStreamsCreate)
|
||||
if !ok {
|
||||
return errors.New("activityCreate: could not convert type to create")
|
||||
}
|
||||
|
||||
// create should have an object
|
||||
object := create.GetActivityStreamsObject()
|
||||
if object == nil {
|
||||
return errors.New("Create had no Object")
|
||||
}
|
||||
|
||||
errs := []string{}
|
||||
// iterate through the object(s) to see what we're meant to be creating
|
||||
for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
|
||||
asObjectType := objectIter.GetType()
|
||||
if asObjectType == nil {
|
||||
// currently we can't do anything with just a Create of something that's not an Object with a type
|
||||
// TODO: process a Create with an Object that's just a URI or something
|
||||
errs = append(errs, "object of Create was not a Type")
|
||||
continue
|
||||
}
|
||||
|
||||
// we have a type -- what is it?
|
||||
asObjectTypeName := asObjectType.GetTypeName()
|
||||
switch asObjectTypeName {
|
||||
case ap.ObjectNote:
|
||||
// CREATE A NOTE
|
||||
if err := f.createNote(ctx, objectIter.GetActivityStreamsNote(), receivingAccount, requestingAccount, fromFederatorChan); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
default:
|
||||
errs = append(errs, fmt.Sprintf("received an object on a Create that we couldn't handle: %s", asObjectType.GetTypeName()))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return fmt.Errorf("activityCreate: one or more errors while processing activity: %s", strings.Join(errs, "; "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createNote handles a Create activity with a Note type.
|
||||
func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStreamsNote, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) error {
|
||||
l := f.log.WithFields(logrus.Fields{
|
||||
"func": "createNote",
|
||||
"receivingAccount": receivingAccount.URI,
|
||||
"requestingAccount": requestingAccount.URI,
|
||||
})
|
||||
|
||||
// Check if we have a forward.
|
||||
// In other words, was the note posted to our inbox by at least one actor who actually created the note, or are they just forwarding it?
|
||||
forward := true
|
||||
|
||||
// note should have an attributedTo
|
||||
noteAttributedTo := note.GetActivityStreamsAttributedTo()
|
||||
if noteAttributedTo == nil {
|
||||
return errors.New("createNote: note had no attributedTo")
|
||||
}
|
||||
|
||||
// compare the attributedTo(s) with the actor who posted this to our inbox
|
||||
for attributedToIter := noteAttributedTo.Begin(); attributedToIter != noteAttributedTo.End(); attributedToIter = attributedToIter.Next() {
|
||||
if !attributedToIter.IsIRI() {
|
||||
continue
|
||||
}
|
||||
iri := attributedToIter.GetIRI()
|
||||
if requestingAccount.URI == iri.String() {
|
||||
// at least one creator of the note, and the actor who posted the note to our inbox, are the same, so it's not a forward
|
||||
forward = false
|
||||
}
|
||||
}
|
||||
|
||||
// If we do have a forward, we should ignore the content for now and just dereference based on the URL/ID of the note instead, to get the note straight from the horse's mouth
|
||||
if forward {
|
||||
l.Trace("note is a forward")
|
||||
id := note.GetJSONLDId()
|
||||
if !id.IsIRI() {
|
||||
// if the note id isn't an IRI, there's nothing we can do here
|
||||
return nil
|
||||
}
|
||||
// pass the note iri into the processor and have it do the dereferencing instead of doing it here
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APIri: id.GetIRI(),
|
||||
GTSModel: nil,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we reach this point, we know it's not a forwarded status, so proceed with processing it as normal
|
||||
|
||||
status, err := f.typeConverter.ASStatusToStatus(ctx, note)
|
||||
if err != nil {
|
||||
return fmt.Errorf("createNote: error converting note to status: %s", err)
|
||||
}
|
||||
|
||||
// id the status based on the time it was created
|
||||
statusID, err := id.NewULIDFromTime(status.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status.ID = statusID
|
||||
|
||||
if err := f.db.PutStatus(ctx, status); err != nil {
|
||||
if err == db.ErrAlreadyExists {
|
||||
// the status already exists in the database, which means we've already handled everything else,
|
||||
// so we can just return nil here and be done with it.
|
||||
return nil
|
||||
}
|
||||
// an actual error has happened
|
||||
return fmt.Errorf("createNote: database error inserting status: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
FOLLOW HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityFollow(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) error {
|
||||
follow, ok := asType.(vocab.ActivityStreamsFollow)
|
||||
if !ok {
|
||||
return errors.New("activityFollow: could not convert type to follow")
|
||||
}
|
||||
|
||||
followRequest, err := f.typeConverter.ASFollowToFollowRequest(ctx, follow)
|
||||
if err != nil {
|
||||
return fmt.Errorf("activityFollow: could not convert Follow to follow request: %s", err)
|
||||
}
|
||||
|
||||
newID, err := id.NewULID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
followRequest.ID = newID
|
||||
|
||||
if err := f.db.Put(ctx, followRequest); err != nil {
|
||||
return fmt.Errorf("activityFollow: database error inserting follow request: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: followRequest,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
LIKE HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityLike(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, fromFederatorChan chan messages.FromFederator) error {
|
||||
like, ok := asType.(vocab.ActivityStreamsLike)
|
||||
if !ok {
|
||||
return errors.New("activityLike: could not convert type to like")
|
||||
}
|
||||
|
||||
fave, err := f.typeConverter.ASLikeToFave(ctx, like)
|
||||
if err != nil {
|
||||
return fmt.Errorf("activityLike: could not convert Like to fave: %s", err)
|
||||
}
|
||||
|
||||
newID, err := id.NewULID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fave.ID = newID
|
||||
|
||||
if err := f.db.Put(ctx, fave); err != nil {
|
||||
return fmt.Errorf("activityLike: database error inserting fave: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- messages.FromFederator{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: fave,
|
||||
ReceivingAccount: receivingAccount,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue