diff options
author | 2023-11-08 14:32:17 +0000 | |
---|---|---|
committer | 2023-11-08 14:32:17 +0000 | |
commit | e9e5dc5a40926e5320cb131b035c46b1e1b0bd59 (patch) | |
tree | 52edc9fa5742f28e1e5223f51cda628ec1c35a24 /internal/ap | |
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/ap')
-rw-r--r-- | internal/ap/extract.go | 121 | ||||
-rw-r--r-- | internal/ap/properties.go | 19 |
2 files changed, 131 insertions, 9 deletions
diff --git a/internal/ap/extract.go b/internal/ap/extract.go index 332550578..b412f251d 100644 --- a/internal/ap/extract.go +++ b/internal/ap/extract.go @@ -22,6 +22,7 @@ import ( "crypto/rsa" "crypto/x509" "encoding/pem" + "errors" "fmt" "net/url" "strings" @@ -1112,6 +1113,91 @@ func ExtractSharedInbox(withEndpoints WithEndpoints) *url.URL { return nil } +// ExtractPoll extracts a placeholder Poll from Pollable interface, with available options and flags populated. +func ExtractPoll(poll Pollable) (*gtsmodel.Poll, error) { + var closed time.Time + + // Extract the options (votes if any) and 'multiple choice' flag. + options, votes, multi, err := ExtractPollOptions(poll) + if err != nil { + return nil, err + } + + // Check if counts have been hidden from us. + hideCounts := len(options) != len(votes) + if hideCounts { + + // Zero out all votes. + for i := range votes { + votes[i] = 0 + } + } + + // Extract the poll end time. + endTime := GetEndTime(poll) + if endTime.IsZero() { + return nil, errors.New("no poll end time specified") + } + + // Extract the poll closed time. + closedSlice := GetClosed(poll) + if len(closedSlice) == 1 { + closed = closedSlice[0] + } + + // Extract the number of voters. + voters := GetVotersCount(poll) + + return >smodel.Poll{ + Options: options, + Multiple: &multi, + HideCounts: &hideCounts, + Votes: votes, + Voters: &voters, + ExpiresAt: endTime, + ClosedAt: closed, + }, nil +} + +// ExtractPollOptions extracts poll option name strings, and the 'multiple choice flag' property value from Pollable. +func ExtractPollOptions(poll Pollable) (names []string, votes []int, multi bool, err error) { + var errs gtserror.MultiError + + // Iterate the oneOf property and gather poll single-choice options. + IterateOneOf(poll, func(iter vocab.ActivityStreamsOneOfPropertyIterator) { + name, count, err := extractPollOption(iter.GetType()) + if err != nil { + errs.Append(err) + return + } + names = append(names, name) + if count != nil { + votes = append(votes, *count) + } + }) + if len(names) > 0 || len(errs) > 0 { + return names, votes, false, errs.Combine() + } + + // Iterate the anyOf property and gather poll multi-choice options. + IterateAnyOf(poll, func(iter vocab.ActivityStreamsAnyOfPropertyIterator) { + name, count, err := extractPollOption(iter.GetType()) + if err != nil { + errs.Append(err) + return + } + names = append(names, name) + if count != nil { + votes = append(votes, *count) + } + }) + if len(names) > 0 || len(errs) > 0 { + return names, votes, true, errs.Combine() + } + + return nil, nil, false, errors.New("poll without options") +} + // IterateOneOf will attempt to extract oneOf property from given interface, and passes each iterated item to function. func IterateOneOf(withOneOf WithOneOf, foreach func(vocab.ActivityStreamsOneOfPropertyIterator)) { if foreach == nil { @@ -1158,6 +1244,41 @@ func IterateAnyOf(withAnyOf WithAnyOf, foreach func(vocab.ActivityStreamsAnyOfPr } } +// extractPollOption extracts a usable poll option name from vocab.Type, or error. +func extractPollOption(t vocab.Type) (name string, votes *int, err error) { + // Check fulfills PollOptionable type + // (this accounts for nil input type). + optionable, ok := t.(PollOptionable) + if !ok { + return "", nil, fmt.Errorf("incorrect option type: %T", t) + } + + // Extract PollOption from interface. + name = ExtractName(optionable) + if name == "" { + return "", nil, errors.New("empty option name") + } + + // Check PollOptionable for attached 'replies' property. + repliesProp := optionable.GetActivityStreamsReplies() + if repliesProp != nil { + + // Get repliesProp as the AS collection type it should be. + collection := repliesProp.GetActivityStreamsCollection() + if collection != nil { + + // Extract integer value from the collection 'totalItems' property. + totalItemsProp := collection.GetActivityStreamsTotalItems() + if totalItemsProp != nil { + i := totalItemsProp.Get() + votes = &i + } + } + } + + return name, votes, nil +} + // isPublic checks if at least one entry in the given // uris slice equals the activitystreams public uri. func isPublic(uris []*url.URL) bool { diff --git a/internal/ap/properties.go b/internal/ap/properties.go index d8441003f..788b8ac4e 100644 --- a/internal/ap/properties.go +++ b/internal/ap/properties.go @@ -24,6 +24,7 @@ import ( "github.com/superseriousbusiness/activity/streams" "github.com/superseriousbusiness/activity/streams/vocab" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" ) // MustGet performs the given 'Get$Property(with) (T, error)' signature function, panicking on error. @@ -36,12 +37,12 @@ import ( // } // MustSet performs the given 'Set$Property(with, T) error' signature function, panicking on error. -// func MustSet[W, T any](fn func(W, T) error, with W, value T) { -// err := fn(with, value) -// if err != nil { -// panicfAt(3, "error setting property on %T: %w", with, err) -// } -// } +func MustSet[W, T any](fn func(W, T) error, with W, value T) { + err := fn(with, value) + if err != nil { + panicfAt(3, "error setting property on %T: %w", with, err) + } +} // AppendSet performs the given 'Append$Property(with, ...T) error' signature function, panicking on error. // func MustAppend[W, T any](fn func(W, ...T) error, with W, values ...T) { @@ -320,6 +321,6 @@ func appendIRIs[T TypeOrIRI](getProp func() Property[T], iri ...*url.URL) { } // panicfAt panics with a call to gtserror.NewfAt() with given args (+1 to calldepth). -// func panicfAt(calldepth int, msg string, args ...any) { -// panic(gtserror.NewfAt(calldepth+1, msg, args...)) -// } +func panicfAt(calldepth int, msg string, args ...any) { + panic(gtserror.NewfAt(calldepth+1, msg, args...)) +} |