diff options
author | 2023-11-08 14:32:17 +0000 | |
---|---|---|
committer | 2023-11-08 14:32:17 +0000 | |
commit | e9e5dc5a40926e5320cb131b035c46b1e1b0bd59 (patch) | |
tree | 52edc9fa5742f28e1e5223f51cda628ec1c35a24 /internal/typeutils/internaltoas.go | |
parent | [chore]: Bump github.com/spf13/cobra from 1.7.0 to 1.8.0 (#2338) (diff) | |
download | gotosocial-e9e5dc5a40926e5320cb131b035c46b1e1b0bd59.tar.xz |
[feature] add support for polls + receiving federated status edits (#2330)
Diffstat (limited to 'internal/typeutils/internaltoas.go')
-rw-r--r-- | internal/typeutils/internaltoas.go | 158 |
1 files changed, 150 insertions, 8 deletions
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 +} |