[feature] tentatively start adding polls support (#2249)

This commit is contained in:
kim 2023-10-04 13:09:42 +01:00 committed by GitHub
commit c6e00afc7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 657 additions and 393 deletions

View file

@ -216,40 +216,10 @@ func (c *Converter) ASRepresentationToAccount(ctx context.Context, accountable a
return acct, nil
}
func (c *Converter) extractAttachments(i ap.WithAttachment) []*gtsmodel.MediaAttachment {
attachmentProp := i.GetActivityStreamsAttachment()
if attachmentProp == nil {
return nil
}
attachments := make([]*gtsmodel.MediaAttachment, 0, attachmentProp.Len())
for iter := attachmentProp.Begin(); iter != attachmentProp.End(); iter = iter.Next() {
t := iter.GetType()
if t == nil {
continue
}
attachmentable, ok := t.(ap.Attachmentable)
if !ok {
log.Error(nil, "ap attachment was not attachmentable")
continue
}
attachment, err := ap.ExtractAttachment(attachmentable)
if err != nil {
log.Errorf(nil, "error extracting attachment: %s", err)
continue
}
attachments = append(attachments, attachment)
}
return attachments
}
// ASStatus converts a remote activitystreams 'status' representation into a gts model status.
func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusable) (*gtsmodel.Status, error) {
var err error
status := new(gtsmodel.Status)
// status.URI
@ -281,7 +251,19 @@ func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
// status.Attachments
//
// Media attachments for later dereferencing.
status.Attachments = c.extractAttachments(statusable)
status.Attachments, err = ap.ExtractAttachments(statusable)
if err != nil {
l.Warnf("error(s) extracting attachments: %v", err)
}
// status.Poll
//
// Attached poll information (the statusable will actually
// be a Pollable, as a Question is a subset of our Status).
if pollable, ok := ap.ToPollable(statusable); ok {
// TODO: handle decoding poll data
_ = pollable
}
// status.Hashtags
//
@ -341,7 +323,7 @@ func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
// error if we don't.
attributedTo, err := ap.ExtractAttributedToURI(statusable)
if err != nil {
return nil, gtserror.Newf("%w", err)
return nil, gtserror.Newf("error extracting attributed to uri: %w", err)
}
accountURI := attributedTo.String()

View file

@ -29,6 +29,7 @@ import (
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
@ -403,21 +404,15 @@ func (c *Converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account)
return person, nil
}
// StatusToAS converts a gts model status into an activity streams note, suitable for federation
func (c *Converter) StatusToAS(ctx context.Context, 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.Account == nil {
a, err := c.state.DB.GetAccountByID(ctx, s.AccountID)
if err != nil {
return nil, gtserror.Newf("error retrieving author account from db: %w", err)
}
s.Account = a
// StatusToAS converts a gts model status into an ActivityStreams Statusable implementation, suitable for federation
func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Statusable, error) {
// Ensure the status model is fully populated.
// The status and poll models are REQUIRED so nothing to do if this fails.
if err := c.state.DB.PopulateStatus(ctx, s); err != nil {
return nil, gtserror.Newf("error populating status: %w", err)
}
// create the Note!
// We convert it as an AS Note.
status := streams.NewActivityStreamsNote()
// id
@ -529,7 +524,6 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.A
}
tagProp.AppendTootHashtag(asHashtag)
}
status.SetActivityStreamsTag(tagProp)
// parse out some URIs we need here
@ -1419,7 +1413,7 @@ func (c *Converter) StatusesToASOutboxPage(ctx context.Context, outboxID string,
return nil, err
}
create, err := c.WrapNoteInCreate(note, true)
create, err := c.WrapStatusableInCreate(note, true)
if err != nil {
return nil, err
}

View file

@ -44,7 +44,6 @@ func (c *Converter) WrapPersonInUpdate(person vocab.ActivityStreamsPerson, origi
update.SetActivityStreamsActor(actorProp)
// set the ID
newID, err := id.NewRandomULID()
if err != nil {
return nil, err
@ -85,26 +84,29 @@ func (c *Converter) WrapPersonInUpdate(person vocab.ActivityStreamsPerson, origi
return update, nil
}
// WrapNoteInCreate wraps a Note with a Create activity.
// WrapNoteInCreate wraps a Statusable with a Create activity.
//
// If objectIRIOnly is set to true, then the function won't put the *entire* note in the Object field of the Create,
// but just the AP URI of the note. This is useful in cases where you want to give a remote server something to dereference,
// and still have control over whether or not they're allowed to actually see the contents.
func (c *Converter) WrapNoteInCreate(note vocab.ActivityStreamsNote, objectIRIOnly bool) (vocab.ActivityStreamsCreate, error) {
func (c *Converter) WrapStatusableInCreate(status ap.Statusable, objectIRIOnly bool) (vocab.ActivityStreamsCreate, error) {
create := streams.NewActivityStreamsCreate()
// Object property
objectProp := streams.NewActivityStreamsObjectProperty()
if objectIRIOnly {
objectProp.AppendIRI(note.GetJSONLDId().GetIRI())
// Only append the object IRI to objectProp.
objectProp.AppendIRI(status.GetJSONLDId().GetIRI())
} else {
objectProp.AppendActivityStreamsNote(note)
// Our statusable's are always note types.
asNote := status.(vocab.ActivityStreamsNote)
objectProp.AppendActivityStreamsNote(asNote)
}
create.SetActivityStreamsObject(objectProp)
// ID property
idProp := streams.NewJSONLDIdProperty()
createID := note.GetJSONLDId().GetIRI().String() + "/activity"
createID := status.GetJSONLDId().GetIRI().String() + "/activity"
createIDIRI, err := url.Parse(createID)
if err != nil {
return nil, err
@ -114,7 +116,7 @@ func (c *Converter) WrapNoteInCreate(note vocab.ActivityStreamsNote, objectIRIOn
// Actor Property
actorProp := streams.NewActivityStreamsActorProperty()
actorIRI, err := ap.ExtractAttributedToURI(note)
actorIRI, err := ap.ExtractAttributedToURI(status)
if err != nil {
return nil, gtserror.Newf("couldn't extract AttributedTo: %w", err)
}
@ -123,7 +125,7 @@ func (c *Converter) WrapNoteInCreate(note vocab.ActivityStreamsNote, objectIRIOn
// Published Property
publishedProp := streams.NewActivityStreamsPublishedProperty()
published, err := ap.ExtractPublished(note)
published, err := ap.ExtractPublished(status)
if err != nil {
return nil, gtserror.Newf("couldn't extract Published: %w", err)
}
@ -132,7 +134,7 @@ func (c *Converter) WrapNoteInCreate(note vocab.ActivityStreamsNote, objectIRIOn
// To Property
toProp := streams.NewActivityStreamsToProperty()
if toURIs := ap.ExtractToURIs(note); len(toURIs) != 0 {
if toURIs := ap.ExtractToURIs(status); len(toURIs) != 0 {
for _, toURI := range toURIs {
toProp.AppendIRI(toURI)
}
@ -141,7 +143,7 @@ func (c *Converter) WrapNoteInCreate(note vocab.ActivityStreamsNote, objectIRIOn
// Cc Property
ccProp := streams.NewActivityStreamsCcProperty()
if ccURIs := ap.ExtractCcURIs(note); len(ccURIs) != 0 {
if ccURIs := ap.ExtractCcURIs(status); len(ccURIs) != 0 {
for _, ccURI := range ccURIs {
ccProp.AppendIRI(ccURI)
}
@ -150,3 +152,64 @@ func (c *Converter) WrapNoteInCreate(note vocab.ActivityStreamsNote, objectIRIOn
return create, nil
}
// WrapStatusableInUpdate wraps a Statusable with an Update activity.
//
// If objectIRIOnly is set to true, then the function won't put the *entire* note in the Object field of the Create,
// but just the AP URI of the note. This is useful in cases where you want to give a remote server something to dereference,
// and still have control over whether or not they're allowed to actually see the contents.
func (c *Converter) WrapStatusableInUpdate(status ap.Statusable, objectIRIOnly bool) (vocab.ActivityStreamsUpdate, error) {
update := streams.NewActivityStreamsUpdate()
// Object property
objectProp := streams.NewActivityStreamsObjectProperty()
if objectIRIOnly {
objectProp.AppendIRI(status.GetJSONLDId().GetIRI())
} else if _, ok := status.(ap.Pollable); ok {
asQuestion := status.(vocab.ActivityStreamsQuestion)
objectProp.AppendActivityStreamsQuestion(asQuestion)
} else {
asNote := status.(vocab.ActivityStreamsNote)
objectProp.AppendActivityStreamsNote(asNote)
}
update.SetActivityStreamsObject(objectProp)
// ID property
idProp := streams.NewJSONLDIdProperty()
createID := status.GetJSONLDId().GetIRI().String() + "/activity"
createIDIRI, err := url.Parse(createID)
if err != nil {
return nil, err
}
idProp.SetIRI(createIDIRI)
update.SetJSONLDId(idProp)
// Actor Property
actorProp := streams.NewActivityStreamsActorProperty()
actorIRI, err := ap.ExtractAttributedToURI(status)
if err != nil {
return nil, gtserror.Newf("couldn't extract AttributedTo: %w", err)
}
actorProp.AppendIRI(actorIRI)
update.SetActivityStreamsActor(actorProp)
// To Property
toProp := streams.NewActivityStreamsToProperty()
if toURIs := ap.ExtractToURIs(status); len(toURIs) != 0 {
for _, toURI := range toURIs {
toProp.AppendIRI(toURI)
}
update.SetActivityStreamsTo(toProp)
}
// Cc Property
ccProp := streams.NewActivityStreamsCcProperty()
if ccURIs := ap.ExtractCcURIs(status); len(ccURIs) != 0 {
for _, ccURI := range ccURIs {
ccProp.AppendIRI(ccURI)
}
update.SetActivityStreamsCc(ccProp)
}
return update, nil
}

View file

@ -36,7 +36,7 @@ func (suite *WrapTestSuite) TestWrapNoteInCreateIRIOnly() {
note, err := suite.typeconverter.StatusToAS(context.Background(), testStatus)
suite.NoError(err)
create, err := suite.typeconverter.WrapNoteInCreate(note, true)
create, err := suite.typeconverter.WrapStatusableInCreate(note, true)
suite.NoError(err)
suite.NotNil(create)
@ -64,7 +64,7 @@ func (suite *WrapTestSuite) TestWrapNoteInCreate() {
note, err := suite.typeconverter.StatusToAS(context.Background(), testStatus)
suite.NoError(err)
create, err := suite.typeconverter.WrapNoteInCreate(note, false)
create, err := suite.typeconverter.WrapStatusableInCreate(note, false)
suite.NoError(err)
suite.NotNil(create)