mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-29 07:52:24 -05:00
[feature] tentatively start adding polls support (#2249)
This commit is contained in:
parent
297b6eeaaa
commit
c6e00afc7c
36 changed files with 657 additions and 393 deletions
|
|
@ -32,10 +32,10 @@ import (
|
|||
func ToCollectionPageIterator(t vocab.Type) (CollectionPageIterator, error) {
|
||||
switch name := t.GetTypeName(); name {
|
||||
case ObjectCollectionPage:
|
||||
t := t.(vocab.ActivityStreamsCollectionPage) //nolint:forcetypeassert
|
||||
t := t.(vocab.ActivityStreamsCollectionPage)
|
||||
return WrapCollectionPage(t), nil
|
||||
case ObjectOrderedCollectionPage:
|
||||
t := t.(vocab.ActivityStreamsOrderedCollectionPage) //nolint:forcetypeassert
|
||||
t := t.(vocab.ActivityStreamsOrderedCollectionPage)
|
||||
return WrapOrderedCollectionPage(t), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%T(%s) was not CollectionPage-like", t, name)
|
||||
|
|
@ -74,7 +74,7 @@ func (iter *regularCollectionPageIterator) PrevPage() WithIRI {
|
|||
return iter.GetActivityStreamsPrev()
|
||||
}
|
||||
|
||||
func (iter *regularCollectionPageIterator) NextItem() IteratorItemable {
|
||||
func (iter *regularCollectionPageIterator) NextItem() TypeOrIRI {
|
||||
if !iter.initItems() {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ func (iter *regularCollectionPageIterator) NextItem() IteratorItemable {
|
|||
return cur
|
||||
}
|
||||
|
||||
func (iter *regularCollectionPageIterator) PrevItem() IteratorItemable {
|
||||
func (iter *regularCollectionPageIterator) PrevItem() TypeOrIRI {
|
||||
if !iter.initItems() {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ func (iter *orderedCollectionPageIterator) PrevPage() WithIRI {
|
|||
return iter.GetActivityStreamsPrev()
|
||||
}
|
||||
|
||||
func (iter *orderedCollectionPageIterator) NextItem() IteratorItemable {
|
||||
func (iter *orderedCollectionPageIterator) NextItem() TypeOrIRI {
|
||||
if !iter.initItems() {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ func (iter *orderedCollectionPageIterator) NextItem() IteratorItemable {
|
|||
return cur
|
||||
}
|
||||
|
||||
func (iter *orderedCollectionPageIterator) PrevItem() IteratorItemable {
|
||||
func (iter *orderedCollectionPageIterator) PrevItem() TypeOrIRI {
|
||||
if !iter.initItems() {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,39 +35,56 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// ExtractObject will extract an object vocab.Type from given implementing interface.
|
||||
func ExtractObject(with WithObject) vocab.Type {
|
||||
// ExtractObjects will extract object vocab.Types from given implementing interface.
|
||||
func ExtractObjects(with WithObject) []TypeOrIRI {
|
||||
// Extract the attached object (if any).
|
||||
obj := with.GetActivityStreamsObject()
|
||||
if obj == nil {
|
||||
objProp := with.GetActivityStreamsObject()
|
||||
if objProp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only support single
|
||||
// objects (for now...)
|
||||
if obj.Len() != 1 {
|
||||
// Check for zero len.
|
||||
if objProp.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extract object vocab.Type.
|
||||
return obj.At(0).GetType()
|
||||
// Accumulate all of the objects into a slice.
|
||||
objs := make([]TypeOrIRI, objProp.Len())
|
||||
for i := 0; i < objProp.Len(); i++ {
|
||||
objs[i] = objProp.At(i)
|
||||
}
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
// ExtractActivityData will extract the usable data type (e.g. Note, Question, etc) and corresponding JSON, from activity.
|
||||
func ExtractActivityData(activity pub.Activity, rawJSON map[string]any) (vocab.Type, map[string]any, bool) {
|
||||
func ExtractActivityData(activity pub.Activity, rawJSON map[string]any) ([]TypeOrIRI, []any, bool) {
|
||||
switch typeName := activity.GetTypeName(); {
|
||||
// Activity (has "object").
|
||||
case isActivity(typeName):
|
||||
objType := ExtractObject(activity)
|
||||
if objType == nil {
|
||||
objTypes := ExtractObjects(activity)
|
||||
if len(objTypes) == 0 {
|
||||
return nil, nil, false
|
||||
}
|
||||
objJSON, _ := rawJSON["object"].(map[string]any)
|
||||
return objType, objJSON, true
|
||||
|
||||
var objJSON []any
|
||||
switch json := rawJSON["object"].(type) {
|
||||
case nil:
|
||||
// do nothing
|
||||
case map[string]any:
|
||||
// Wrap map in slice.
|
||||
objJSON = []any{json}
|
||||
case []any:
|
||||
// Use existing slice.
|
||||
objJSON = json
|
||||
}
|
||||
|
||||
return objTypes, objJSON, true
|
||||
|
||||
// IntransitiveAcitivity (no "object").
|
||||
case isIntransitiveActivity(typeName):
|
||||
return activity, rawJSON, false
|
||||
asTypeOrIRI := _TypeOrIRI{activity} // wrap activity.
|
||||
return []TypeOrIRI{&asTypeOrIRI}, []any{rawJSON}, true
|
||||
|
||||
// Unknown.
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -247,14 +247,8 @@ type CollectionPageIterator interface {
|
|||
NextPage() WithIRI
|
||||
PrevPage() WithIRI
|
||||
|
||||
NextItem() IteratorItemable
|
||||
PrevItem() IteratorItemable
|
||||
}
|
||||
|
||||
// IteratorItemable represents the minimum interface for an item in an iterator.
|
||||
type IteratorItemable interface {
|
||||
WithIRI
|
||||
WithType
|
||||
NextItem() TypeOrIRI
|
||||
PrevItem() TypeOrIRI
|
||||
}
|
||||
|
||||
// Flaggable represents the minimum interface for an activitystreams 'Flag' activity.
|
||||
|
|
@ -267,6 +261,12 @@ type Flaggable interface {
|
|||
WithObject
|
||||
}
|
||||
|
||||
// TypeOrIRI represents the minimum interface for something that may be a vocab.Type OR IRI.
|
||||
type TypeOrIRI interface {
|
||||
WithIRI
|
||||
WithType
|
||||
}
|
||||
|
||||
// WithJSONLDId represents an activity with JSONLDIdProperty.
|
||||
type WithJSONLDId interface {
|
||||
GetJSONLDId() vocab.JSONLDIdProperty
|
||||
|
|
|
|||
|
|
@ -39,60 +39,48 @@ import (
|
|||
// This function is a noop if the type passed in is anything except a Create or Update with a Statusable or Accountable as its Object.
|
||||
func NormalizeIncomingActivity(activity pub.Activity, rawJSON map[string]interface{}) {
|
||||
// From the activity extract the data vocab.Type + its "raw" JSON.
|
||||
dataType, rawData, ok := ExtractActivityData(activity, rawJSON)
|
||||
if !ok {
|
||||
dataIfaces, rawData, ok := ExtractActivityData(activity, rawJSON)
|
||||
if !ok || len(dataIfaces) != len(rawData) {
|
||||
// non-equal lengths *shouldn't* happen,
|
||||
// but this is just an integrity check.
|
||||
return
|
||||
}
|
||||
|
||||
switch dataType.GetTypeName() {
|
||||
// "Pollable" types.
|
||||
case ActivityQuestion:
|
||||
pollable, ok := dataType.(Pollable)
|
||||
if !ok {
|
||||
return
|
||||
// Iterate over the available data.
|
||||
for i, dataIface := range dataIfaces {
|
||||
// Try to get as vocab.Type, else
|
||||
// skip this entry for normalization.
|
||||
dataType := dataIface.GetType()
|
||||
if dataType == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Normalize the Pollable specific properties.
|
||||
NormalizeIncomingPollOptions(pollable, rawData)
|
||||
|
||||
// Fallthrough to handle
|
||||
// the rest as Statusable.
|
||||
fallthrough
|
||||
|
||||
// "Statusable" types.
|
||||
case ObjectArticle,
|
||||
ObjectDocument,
|
||||
ObjectImage,
|
||||
ObjectVideo,
|
||||
ObjectNote,
|
||||
ObjectPage,
|
||||
ObjectEvent,
|
||||
ObjectPlace,
|
||||
ObjectProfile:
|
||||
statusable, ok := dataType.(Statusable)
|
||||
// Get the raw data map at index, else skip
|
||||
// this entry due to impossible normalization.
|
||||
rawData, ok := rawData[i].(map[string]any)
|
||||
if !ok {
|
||||
return
|
||||
continue
|
||||
}
|
||||
|
||||
// Normalize everything we can on the statusable.
|
||||
NormalizeIncomingContent(statusable, rawData)
|
||||
NormalizeIncomingAttachments(statusable, rawData)
|
||||
NormalizeIncomingSummary(statusable, rawData)
|
||||
NormalizeIncomingName(statusable, rawData)
|
||||
if statusable, ok := ToStatusable(dataType); ok {
|
||||
if pollable, ok := ToPollable(dataType); ok {
|
||||
// Normalize the Pollable specific properties.
|
||||
NormalizeIncomingPollOptions(pollable, rawData)
|
||||
}
|
||||
|
||||
// "Accountable" types.
|
||||
case ActorApplication,
|
||||
ActorGroup,
|
||||
ActorOrganization,
|
||||
ActorPerson,
|
||||
ActorService:
|
||||
accountable, ok := dataType.(Accountable)
|
||||
if !ok {
|
||||
return
|
||||
// Normalize everything we can on the statusable.
|
||||
NormalizeIncomingContent(statusable, rawData)
|
||||
NormalizeIncomingAttachments(statusable, rawData)
|
||||
NormalizeIncomingSummary(statusable, rawData)
|
||||
NormalizeIncomingName(statusable, rawData)
|
||||
continue
|
||||
}
|
||||
|
||||
// Normalize everything we can on the accountable.
|
||||
NormalizeIncomingSummary(accountable, rawData)
|
||||
if accountable, ok := ToAccountable(dataType); ok {
|
||||
// Normalize everything we can on the accountable.
|
||||
NormalizeIncomingSummary(accountable, rawData)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
43
internal/ap/util.go
Normal file
43
internal/ap/util.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ap
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
)
|
||||
|
||||
// _TypeOrIRI wraps a vocab.Type to implement TypeOrIRI.
|
||||
type _TypeOrIRI struct {
|
||||
vocab.Type
|
||||
}
|
||||
|
||||
func (t *_TypeOrIRI) GetType() vocab.Type {
|
||||
return t.Type
|
||||
}
|
||||
|
||||
func (t *_TypeOrIRI) GetIRI() *url.URL {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *_TypeOrIRI) IsIRI() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *_TypeOrIRI) SetIRI(*url.URL) {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue