summaryrefslogtreecommitdiff
path: root/internal/typeutils
diff options
context:
space:
mode:
Diffstat (limited to 'internal/typeutils')
-rw-r--r--internal/typeutils/astointernal.go6
-rw-r--r--internal/typeutils/internaltoas.go158
-rw-r--r--internal/typeutils/internaltoas_test.go4
-rw-r--r--internal/typeutils/internaltofrontend.go101
-rw-r--r--internal/typeutils/internaltofrontend_test.go50
-rw-r--r--internal/typeutils/wrap.go175
-rw-r--r--internal/typeutils/wrap_test.go9
7 files changed, 349 insertions, 154 deletions
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go
index 92465c790..707f51629 100644
--- a/internal/typeutils/astointernal.go
+++ b/internal/typeutils/astointernal.go
@@ -261,8 +261,10 @@ func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
// 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.Poll, err = ap.ExtractPoll(pollable)
+ if err != nil {
+ l.Warnf("error(s) extracting poll: %v", err)
+ }
}
// status.Hashtags
diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go
index b920d9a0e..a668989e6 100644
--- a/internal/typeutils/internaltoas.go
+++ b/internal/typeutils/internaltoas.go
@@ -412,8 +412,24 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat
return nil, gtserror.Newf("error populating status: %w", err)
}
- // We convert it as an AS Note.
- status := streams.NewActivityStreamsNote()
+ var status ap.Statusable
+
+ if s.Poll != nil {
+ // If status has poll available, we convert
+ // it as an AS Question (similar to a Note).
+ poll := streams.NewActivityStreamsQuestion()
+
+ // Add required status poll data to AS Question.
+ if err := c.addPollToAS(ctx, s.Poll, poll); err != nil {
+ return nil, gtserror.Newf("error converting poll: %w", err)
+ }
+
+ // Set poll as status.
+ status = poll
+ } else {
+ // Else we converter it as an AS Note.
+ status = streams.NewActivityStreamsNote()
+ }
// id
statusURI, err := url.Parse(s.URI)
@@ -636,6 +652,73 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat
return status, nil
}
+func (c *Converter) addPollToAS(ctx context.Context, poll *gtsmodel.Poll, dst ap.Pollable) error {
+ var optionsProp interface {
+ // the minimum interface for appending AS Notes
+ // to an AS type options property of some kind.
+ AppendActivityStreamsNote(vocab.ActivityStreamsNote)
+ }
+
+ if len(poll.Options) != len(poll.Votes) {
+ return gtserror.Newf("invalid poll %s", poll.ID)
+ }
+
+ if !*poll.HideCounts {
+ // Set total no. voting accounts.
+ ap.SetVotersCount(dst, *poll.Voters)
+ }
+
+ if *poll.Multiple {
+ // Create new multiple-choice (AnyOf) property for poll.
+ anyOfProp := streams.NewActivityStreamsAnyOfProperty()
+ dst.SetActivityStreamsAnyOf(anyOfProp)
+ optionsProp = anyOfProp
+ } else {
+ // Create new single-choice (OneOf) property for poll.
+ oneOfProp := streams.NewActivityStreamsOneOfProperty()
+ dst.SetActivityStreamsOneOf(oneOfProp)
+ optionsProp = oneOfProp
+ }
+
+ for i, name := range poll.Options {
+ // Create new Note object to represent option.
+ note := streams.NewActivityStreamsNote()
+
+ // Create new name property and set the option name.
+ nameProp := streams.NewActivityStreamsNameProperty()
+ nameProp.AppendXMLSchemaString(name)
+ note.SetActivityStreamsName(nameProp)
+
+ if !*poll.HideCounts {
+ // Create new total items property to hold the vote count.
+ totalItemsProp := streams.NewActivityStreamsTotalItemsProperty()
+ totalItemsProp.Set(poll.Votes[i])
+
+ // Create new replies property with collection to encompass count.
+ repliesProp := streams.NewActivityStreamsRepliesProperty()
+ collection := streams.NewActivityStreamsCollection()
+ collection.SetActivityStreamsTotalItems(totalItemsProp)
+ repliesProp.SetActivityStreamsCollection(collection)
+
+ // Attach the replies to Note object.
+ note.SetActivityStreamsReplies(repliesProp)
+ }
+
+ // Append the note to options property.
+ optionsProp.AppendActivityStreamsNote(note)
+ }
+
+ // Set poll endTime property.
+ ap.SetEndTime(dst, poll.ExpiresAt)
+
+ if !poll.ClosedAt.IsZero() {
+ // Poll is closed, set closed property.
+ ap.AppendClosed(dst, poll.ClosedAt)
+ }
+
+ return nil
+}
+
// StatusToASDelete converts a gts model status into a Delete of that status, using just the
// URI of the status as object, and addressing the Delete appropriately.
func (c *Converter) StatusToASDelete(ctx context.Context, s *gtsmodel.Status) (vocab.ActivityStreamsDelete, error) {
@@ -1413,12 +1496,8 @@ func (c *Converter) StatusesToASOutboxPage(ctx context.Context, outboxID string,
return nil, err
}
- create, err := c.WrapStatusableInCreate(note, true)
- if err != nil {
- return nil, err
- }
-
- itemsProp.AppendActivityStreamsCreate(create)
+ activity := WrapStatusableInCreate(note, true)
+ itemsProp.AppendActivityStreamsCreate(activity)
if highest == "" || s.ID > highest {
highest = s.ID
@@ -1569,3 +1648,66 @@ func (c *Converter) ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (voc
return flag, nil
}
+
+func (c *Converter) PollVoteToASOptions(ctx context.Context, vote *gtsmodel.PollVote) ([]ap.PollOptionable, error) {
+ // Ensure the vote is fully populated (this fetches author).
+ if err := c.state.DB.PopulatePollVote(ctx, vote); err != nil {
+ return nil, gtserror.Newf("error populating vote from db: %w", err)
+ }
+
+ // Get the vote author.
+ author := vote.Account
+
+ // Get the JSONLD ID IRI for vote author.
+ authorIRI, err := url.Parse(author.URI)
+ if err != nil {
+ return nil, gtserror.Newf("invalid author uri: %w", err)
+ }
+
+ // Get the vote poll.
+ poll := vote.Poll
+
+ // Ensure the poll is fully populated with status.
+ if err := c.state.DB.PopulatePoll(ctx, poll); err != nil {
+ return nil, gtserror.Newf("error populating poll from db: %w", err)
+ }
+
+ // Get the JSONLD ID IRI for poll's source status.
+ statusIRI, err := url.Parse(poll.Status.URI)
+ if err != nil {
+ return nil, gtserror.Newf("invalid status uri: %w", err)
+ }
+
+ // Get the JSONLD ID IRI for poll's author account.
+ pollAuthorIRI, err := url.Parse(poll.Status.AccountURI)
+ if err != nil {
+ return nil, gtserror.Newf("invalid account uri: %w", err)
+ }
+
+ // Preallocate the return slice of notes.
+ notes := make([]ap.PollOptionable, len(vote.Choices))
+
+ for i, choice := range vote.Choices {
+ // Create new note to represent vote.
+ note := streams.NewActivityStreamsNote()
+
+ // For AP IRI generate from author URI + poll ID + vote choice.
+ id := fmt.Sprintf("%s#%s/votes/%d", author.URI, poll.ID, choice)
+ ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(note), id)
+
+ // Attach new name property to note with vote choice.
+ nameProp := streams.NewActivityStreamsNameProperty()
+ nameProp.AppendXMLSchemaString(poll.Options[choice])
+ note.SetActivityStreamsName(nameProp)
+
+ // Set 'to', 'attribTo', 'inReplyTo' fields.
+ ap.AppendAttributedTo(note, authorIRI)
+ ap.AppendInReplyTo(note, statusIRI)
+ ap.AppendTo(note, pollAuthorIRI)
+
+ // Set note in return slice.
+ notes[i] = note
+ }
+
+ return notes, nil
+}
diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go
index 30e4f2135..01dde66fb 100644
--- a/internal/typeutils/internaltoas_test.go
+++ b/internal/typeutils/internaltoas_test.go
@@ -680,7 +680,7 @@ func (suite *InternalToASTestSuite) TestStatusesToASOutboxPage() {
{
"actor": "http://localhost:8080/users/admin",
"cc": "http://localhost:8080/users/admin/followers",
- "id": "http://localhost:8080/users/admin/statuses/01F8MHAAY43M6RJ473VQFCVH37/activity",
+ "id": "http://localhost:8080/users/admin/statuses/01F8MHAAY43M6RJ473VQFCVH37/activity#Create",
"object": "http://localhost:8080/users/admin/statuses/01F8MHAAY43M6RJ473VQFCVH37",
"published": "2021-10-20T12:36:45Z",
"to": "https://www.w3.org/ns/activitystreams#Public",
@@ -689,7 +689,7 @@ func (suite *InternalToASTestSuite) TestStatusesToASOutboxPage() {
{
"actor": "http://localhost:8080/users/admin",
"cc": "http://localhost:8080/users/admin/followers",
- "id": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R/activity",
+ "id": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R/activity#Create",
"object": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
"published": "2021-10-20T11:36:45Z",
"to": "https://www.w3.org/ns/activitystreams#Public",
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index 254bf9da3..6a374bbde 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -729,9 +729,12 @@ func (c *Converter) StatusToAPIStatus(ctx context.Context, s *gtsmodel.Status, r
}
if appID := s.CreatedWithApplicationID; appID != "" {
- app, err := c.state.DB.GetApplicationByID(ctx, appID)
- if err != nil {
- return nil, fmt.Errorf("error getting application %s: %w", appID, err)
+ app := s.CreatedWithApplication
+ if app == nil {
+ app, err = c.state.DB.GetApplicationByID(ctx, appID)
+ if err != nil {
+ return nil, fmt.Errorf("error getting application %s: %w", appID, err)
+ }
}
apiApp, err := c.AppToAPIAppPublic(ctx, app)
@@ -742,6 +745,18 @@ func (c *Converter) StatusToAPIStatus(ctx context.Context, s *gtsmodel.Status, r
apiStatus.Application = apiApp
}
+ if s.Poll != nil {
+ // Set originating
+ // status on the poll.
+ poll := s.Poll
+ poll.Status = s
+
+ apiStatus.Poll, err = c.PollToAPIPoll(ctx, requestingAccount, poll)
+ if err != nil {
+ return nil, fmt.Errorf("error converting poll: %w", err)
+ }
+ }
+
// Normalization.
if s.URL == "" {
@@ -1287,6 +1302,86 @@ func (c *Converter) MarkersToAPIMarker(ctx context.Context, markers []*gtsmodel.
return apiMarker, nil
}
+// PollToAPIPoll converts a database (gtsmodel) Poll into an API model representation appropriate for the given requesting account.
+func (c *Converter) PollToAPIPoll(ctx context.Context, requester *gtsmodel.Account, poll *gtsmodel.Poll) (*apimodel.Poll, error) {
+ // Ensure the poll model is fully populated for src status.
+ if err := c.state.DB.PopulatePoll(ctx, poll); err != nil {
+ return nil, gtserror.Newf("error populating poll: %w", err)
+ }
+
+ var (
+ totalVotes int
+ totalVoters int
+ voteCounts []int
+ ownChoices []int
+ isAuthor bool
+ )
+
+ if requester != nil {
+ // Get vote by requester in poll (if any).
+ vote, err := c.state.DB.GetPollVoteBy(ctx,
+ poll.ID,
+ requester.ID,
+ )
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ return nil, gtserror.Newf("error getting vote for poll %s: %w", poll.ID, err)
+ }
+
+ if vote != nil {
+ // Set choices by requester.
+ ownChoices = vote.Choices
+
+ // Update default totals in the
+ // case that counts are hidden.
+ totalVotes = len(vote.Choices)
+ totalVoters = 1
+ }
+
+ // Check if requester is author of source status.
+ isAuthor = (requester.ID == poll.Status.AccountID)
+ }
+
+ // Preallocate a slice of frontend model poll choices.
+ options := make([]apimodel.PollOption, len(poll.Options))
+
+ // Add the titles to all of the options.
+ for i, title := range poll.Options {
+ options[i].Title = title
+ }
+
+ if isAuthor || !*poll.HideCounts {
+ // A remote status,
+ // the simple route!
+ //
+ // Pull cached remote values.
+ totalVoters = *poll.Voters
+ voteCounts = poll.Votes
+
+ // Accumulate total from all counts.
+ for _, count := range poll.Votes {
+ totalVotes += count
+ }
+
+ // When this is status author, or hide counts
+ // is disabled, set the counts known per vote.
+ for i, count := range voteCounts {
+ options[i].VotesCount = count
+ }
+ }
+
+ return &apimodel.Poll{
+ ID: poll.ID,
+ ExpiresAt: util.FormatISO8601(poll.ExpiresAt),
+ Expired: poll.Closed(),
+ Multiple: *poll.Multiple,
+ VotesCount: totalVotes,
+ VotersCount: totalVoters,
+ Voted: (isAuthor || len(ownChoices) > 0),
+ OwnVotes: ownChoices,
+ Options: options,
+ }, nil
+}
+
// convertAttachmentsToAPIAttachments will convert a slice of GTS model attachments to frontend API model attachments, falling back to IDs if no GTS models supplied.
func (c *Converter) convertAttachmentsToAPIAttachments(ctx context.Context, attachments []*gtsmodel.MediaAttachment, attachmentIDs []string) ([]apimodel.Attachment, error) {
var errs gtserror.MultiError
diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go
index 16966d8cb..0e09faeea 100644
--- a/internal/typeutils/internaltofrontend_test.go
+++ b/internal/typeutils/internaltofrontend_test.go
@@ -58,8 +58,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontend() {
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"followers_count": 2,
"following_count": 2,
- "statuses_count": 5,
- "last_status_at": "2022-05-20T11:37:55.000Z",
+ "statuses_count": 6,
+ "last_status_at": "2022-05-20T11:41:10.000Z",
"emojis": [],
"fields": [],
"enable_rss": true,
@@ -100,8 +100,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiStruct()
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"followers_count": 2,
"following_count": 2,
- "statuses_count": 5,
- "last_status_at": "2022-05-20T11:37:55.000Z",
+ "statuses_count": 6,
+ "last_status_at": "2022-05-20T11:41:10.000Z",
"emojis": [
{
"shortcode": "rainbow",
@@ -148,8 +148,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiIDs() {
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"followers_count": 2,
"following_count": 2,
- "statuses_count": 5,
- "last_status_at": "2022-05-20T11:37:55.000Z",
+ "statuses_count": 6,
+ "last_status_at": "2022-05-20T11:41:10.000Z",
"emojis": [
{
"shortcode": "rainbow",
@@ -192,8 +192,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() {
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"followers_count": 2,
"following_count": 2,
- "statuses_count": 5,
- "last_status_at": "2022-05-20T11:37:55.000Z",
+ "statuses_count": 6,
+ "last_status_at": "2022-05-20T11:41:10.000Z",
"emojis": [],
"fields": [],
"source": {
@@ -660,7 +660,7 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
},
"stats": {
"domain_count": 2,
- "status_count": 16,
+ "status_count": 18,
"user_count": 4
},
"thumbnail": "http://localhost:8080/assets/logo.png",
@@ -910,8 +910,8 @@ func (suite *InternalToFrontendTestSuite) TestReportToFrontend1() {
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 0,
"following_count": 0,
- "statuses_count": 1,
- "last_status_at": "2021-09-20T10:40:37.000Z",
+ "statuses_count": 2,
+ "last_status_at": "2021-09-11T09:40:37.000Z",
"emojis": [],
"fields": []
}
@@ -953,8 +953,8 @@ func (suite *InternalToFrontendTestSuite) TestReportToFrontend2() {
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 1,
"following_count": 1,
- "statuses_count": 7,
- "last_status_at": "2021-10-20T10:40:37.000Z",
+ "statuses_count": 8,
+ "last_status_at": "2021-07-28T08:40:37.000Z",
"emojis": [],
"fields": [
{
@@ -1027,8 +1027,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 0,
"following_count": 0,
- "statuses_count": 1,
- "last_status_at": "2021-09-20T10:40:37.000Z",
+ "statuses_count": 2,
+ "last_status_at": "2021-09-11T09:40:37.000Z",
"emojis": [],
"fields": []
}
@@ -1068,8 +1068,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 1,
"following_count": 1,
- "statuses_count": 7,
- "last_status_at": "2021-10-20T10:40:37.000Z",
+ "statuses_count": 8,
+ "last_status_at": "2021-07-28T08:40:37.000Z",
"emojis": [],
"fields": [
{
@@ -1239,8 +1239,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 1,
"following_count": 1,
- "statuses_count": 7,
- "last_status_at": "2021-10-20T10:40:37.000Z",
+ "statuses_count": 8,
+ "last_status_at": "2021-07-28T08:40:37.000Z",
"emojis": [],
"fields": [
{
@@ -1295,8 +1295,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 0,
"following_count": 0,
- "statuses_count": 1,
- "last_status_at": "2021-09-20T10:40:37.000Z",
+ "statuses_count": 2,
+ "last_status_at": "2021-09-11T09:40:37.000Z",
"emojis": [],
"fields": []
}
@@ -1342,8 +1342,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 0,
"following_count": 0,
- "statuses_count": 1,
- "last_status_at": "2021-09-20T10:40:37.000Z",
+ "statuses_count": 2,
+ "last_status_at": "2021-09-11T09:40:37.000Z",
"emojis": [],
"fields": []
},
@@ -1473,8 +1473,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontendSuspendedLoca
"header_static": "http://localhost:8080/assets/default_header.png",
"followers_count": 0,
"following_count": 0,
- "statuses_count": 1,
- "last_status_at": "2021-09-20T10:40:37.000Z",
+ "statuses_count": 2,
+ "last_status_at": "2021-09-11T09:40:37.000Z",
"emojis": [],
"fields": []
}
diff --git a/internal/typeutils/wrap.go b/internal/typeutils/wrap.go
index 128c4ef15..5deca0e5b 100644
--- a/internal/typeutils/wrap.go
+++ b/internal/typeutils/wrap.go
@@ -19,6 +19,7 @@ package typeutils
import (
"net/url"
+ "time"
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
@@ -84,132 +85,86 @@ func (c *Converter) WrapPersonInUpdate(person vocab.ActivityStreamsPerson, origi
return update, nil
}
-// 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) WrapStatusableInCreate(status ap.Statusable, objectIRIOnly bool) (vocab.ActivityStreamsCreate, error) {
+func WrapStatusableInCreate(status ap.Statusable, iriOnly bool) vocab.ActivityStreamsCreate {
create := streams.NewActivityStreamsCreate()
+ wrapStatusableInActivity(create, status, iriOnly)
+ return create
+}
- // Object property
- objectProp := streams.NewActivityStreamsObjectProperty()
- if objectIRIOnly {
- // Only append the object IRI to objectProp.
- objectProp.AppendIRI(status.GetJSONLDId().GetIRI())
- } else {
- // Our statusable's are always note types.
- asNote := status.(vocab.ActivityStreamsNote)
- objectProp.AppendActivityStreamsNote(asNote)
+func WrapPollOptionablesInCreate(options ...ap.PollOptionable) vocab.ActivityStreamsCreate {
+ if len(options) == 0 {
+ panic("no options")
}
- create.SetActivityStreamsObject(objectProp)
- // ID property
- idProp := streams.NewJSONLDIdProperty()
- createID := status.GetJSONLDId().GetIRI().String() + "/activity"
- createIDIRI, err := url.Parse(createID)
- if err != nil {
- return nil, err
+ // Extract attributedTo IRI from any option.
+ attribTos := ap.GetAttributedTo(options[0])
+ if len(attribTos) != 1 {
+ panic("invalid attributedTo count")
}
- idProp.SetIRI(createIDIRI)
- create.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)
+ // Extract target status IRI from any option.
+ replyTos := ap.GetInReplyTo(options[0])
+ if len(replyTos) != 1 {
+ panic("invalid inReplyTo count")
}
- actorProp.AppendIRI(actorIRI)
- create.SetActivityStreamsActor(actorProp)
- // Published Property
- publishedProp := streams.NewActivityStreamsPublishedProperty()
- published, err := ap.ExtractPublished(status)
- if err != nil {
- return nil, gtserror.Newf("couldn't extract Published: %w", err)
- }
- publishedProp.Set(published)
- create.SetActivityStreamsPublished(publishedProp)
+ // Allocate create activity and copy over 'To' property.
+ create := streams.NewActivityStreamsCreate()
+ ap.AppendTo(create, ap.GetTo(options[0])...)
- // To Property
- toProp := streams.NewActivityStreamsToProperty()
- if toURIs := ap.ExtractToURIs(status); len(toURIs) != 0 {
- for _, toURI := range toURIs {
- toProp.AppendIRI(toURI)
- }
- create.SetActivityStreamsTo(toProp)
- }
+ // Activity ID formatted as: {$statusIRI}/activity#vote/{$voterIRI}.
+ id := replyTos[0].String() + "/activity#vote/" + attribTos[0].String()
+ ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(create), id)
+
+ // Set a current publish time for activity.
+ ap.SetPublished(create, time.Now())
- // Cc Property
- ccProp := streams.NewActivityStreamsCcProperty()
- if ccURIs := ap.ExtractCcURIs(status); len(ccURIs) != 0 {
- for _, ccURI := range ccURIs {
- ccProp.AppendIRI(ccURI)
- }
- create.SetActivityStreamsCc(ccProp)
+ // Append each poll option as object to activity.
+ for _, option := range options {
+ status, _ := ap.ToStatusable(option)
+ appendStatusableToActivity(create, status, false)
}
- return create, nil
+ return create
}
-// 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) {
+func WrapStatusableInUpdate(status ap.Statusable, iriOnly bool) vocab.ActivityStreamsUpdate {
update := streams.NewActivityStreamsUpdate()
+ wrapStatusableInActivity(update, status, iriOnly)
+ return update
+}
- // 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)
- }
+// wrapStatusableInActivity adds the required ap.Statusable data to the given ap.Activityable.
+func wrapStatusableInActivity(activity ap.Activityable, status ap.Statusable, iriOnly bool) {
+ idIRI := ap.GetJSONLDId(status) // activity ID formatted as {$statusIRI}/activity#{$typeName}
+ ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(activity), idIRI.String()+"/activity#"+activity.GetTypeName())
+ appendStatusableToActivity(activity, status, iriOnly)
+ ap.AppendTo(activity, ap.GetTo(status)...)
+ ap.AppendCc(activity, ap.GetCc(status)...)
+ ap.AppendActor(activity, ap.GetAttributedTo(status)...)
+ ap.SetPublished(activity, ap.GetPublished(status))
+}
- // Cc Property
- ccProp := streams.NewActivityStreamsCcProperty()
- if ccURIs := ap.ExtractCcURIs(status); len(ccURIs) != 0 {
- for _, ccURI := range ccURIs {
- ccProp.AppendIRI(ccURI)
- }
- update.SetActivityStreamsCc(ccProp)
+// appendStatusableToActivity appends a Statusable type to an Activityable, handling case of Question, Note or just IRI type.
+func appendStatusableToActivity(activity ap.Activityable, status ap.Statusable, iriOnly bool) {
+ // Get existing object property or allocate new.
+ objProp := activity.GetActivityStreamsObject()
+ if objProp == nil {
+ objProp = streams.NewActivityStreamsObjectProperty()
+ activity.SetActivityStreamsObject(objProp)
+ }
+
+ if iriOnly {
+ // Only append status IRI.
+ idIRI := ap.GetJSONLDId(status)
+ objProp.AppendIRI(idIRI)
+ } else if poll, ok := ap.ToPollable(status); ok {
+ // Our Pollable implementer is an AS Question type.
+ question := poll.(vocab.ActivityStreamsQuestion)
+ objProp.AppendActivityStreamsQuestion(question)
+ } else {
+ // All of our other Statusable types are AS Note.
+ note := status.(vocab.ActivityStreamsNote)
+ objProp.AppendActivityStreamsNote(note)
}
-
- return update, nil
}
diff --git a/internal/typeutils/wrap_test.go b/internal/typeutils/wrap_test.go
index 51f67f455..9d6d95983 100644
--- a/internal/typeutils/wrap_test.go
+++ b/internal/typeutils/wrap_test.go
@@ -24,6 +24,7 @@ import (
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/ap"
+ "github.com/superseriousbusiness/gotosocial/internal/typeutils"
)
type WrapTestSuite struct {
@@ -36,7 +37,7 @@ func (suite *WrapTestSuite) TestWrapNoteInCreateIRIOnly() {
note, err := suite.typeconverter.StatusToAS(context.Background(), testStatus)
suite.NoError(err)
- create, err := suite.typeconverter.WrapStatusableInCreate(note, true)
+ create := typeutils.WrapStatusableInCreate(note, true)
suite.NoError(err)
suite.NotNil(create)
@@ -50,7 +51,7 @@ func (suite *WrapTestSuite) TestWrapNoteInCreateIRIOnly() {
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "http://localhost:8080/users/the_mighty_zork",
"cc": "http://localhost:8080/users/the_mighty_zork/followers",
- "id": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/activity",
+ "id": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/activity#Create",
"object": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
"published": "2021-10-20T12:40:37+02:00",
"to": "https://www.w3.org/ns/activitystreams#Public",
@@ -64,7 +65,7 @@ func (suite *WrapTestSuite) TestWrapNoteInCreate() {
note, err := suite.typeconverter.StatusToAS(context.Background(), testStatus)
suite.NoError(err)
- create, err := suite.typeconverter.WrapStatusableInCreate(note, false)
+ create := typeutils.WrapStatusableInCreate(note, false)
suite.NoError(err)
suite.NotNil(create)
@@ -78,7 +79,7 @@ func (suite *WrapTestSuite) TestWrapNoteInCreate() {
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "http://localhost:8080/users/the_mighty_zork",
"cc": "http://localhost:8080/users/the_mighty_zork/followers",
- "id": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/activity",
+ "id": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/activity#Create",
"object": {
"attachment": [],
"attributedTo": "http://localhost:8080/users/the_mighty_zork",