mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-22 11:46:40 +00:00
[chore] internal/ap: add pollable AS types, code reformatting, general niceties (#2248)
This commit is contained in:
parent
a1ab2c255a
commit
297b6eeaaa
|
@ -78,3 +78,49 @@
|
||||||
// and https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tag
|
// and https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tag
|
||||||
TagHashtag = "Hashtag"
|
TagHashtag = "Hashtag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// isActivity returns whether AS type name is of an Activity (NOT IntransitiveActivity).
|
||||||
|
func isActivity(typeName string) bool {
|
||||||
|
switch typeName {
|
||||||
|
case ActivityAccept,
|
||||||
|
ActivityTentativeAccept,
|
||||||
|
ActivityAdd,
|
||||||
|
ActivityCreate,
|
||||||
|
ActivityDelete,
|
||||||
|
ActivityFollow,
|
||||||
|
ActivityIgnore,
|
||||||
|
ActivityJoin,
|
||||||
|
ActivityLeave,
|
||||||
|
ActivityLike,
|
||||||
|
ActivityOffer,
|
||||||
|
ActivityInvite,
|
||||||
|
ActivityReject,
|
||||||
|
ActivityTentativeReject,
|
||||||
|
ActivityRemove,
|
||||||
|
ActivityUndo,
|
||||||
|
ActivityUpdate,
|
||||||
|
ActivityView,
|
||||||
|
ActivityListen,
|
||||||
|
ActivityRead,
|
||||||
|
ActivityMove,
|
||||||
|
ActivityAnnounce,
|
||||||
|
ActivityBlock,
|
||||||
|
ActivityFlag,
|
||||||
|
ActivityDislike:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isIntransitiveActivity returns whether AS type name is of an IntransitiveActivity.
|
||||||
|
func isIntransitiveActivity(typeName string) bool {
|
||||||
|
switch typeName {
|
||||||
|
case ActivityArrive,
|
||||||
|
ActivityTravel,
|
||||||
|
ActivityQuestion:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,12 +28,53 @@
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/superseriousbusiness/activity/pub"
|
"github.com/superseriousbusiness/activity/pub"
|
||||||
|
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/text"
|
"github.com/superseriousbusiness/gotosocial/internal/text"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ExtractObject will extract an object vocab.Type from given implementing interface.
|
||||||
|
func ExtractObject(with WithObject) vocab.Type {
|
||||||
|
// Extract the attached object (if any).
|
||||||
|
obj := with.GetActivityStreamsObject()
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only support single
|
||||||
|
// objects (for now...)
|
||||||
|
if obj.Len() != 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract object vocab.Type.
|
||||||
|
return obj.At(0).GetType()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
switch typeName := activity.GetTypeName(); {
|
||||||
|
// Activity (has "object").
|
||||||
|
case isActivity(typeName):
|
||||||
|
objType := ExtractObject(activity)
|
||||||
|
if objType == nil {
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
objJSON, _ := rawJSON["object"].(map[string]any)
|
||||||
|
return objType, objJSON, true
|
||||||
|
|
||||||
|
// IntransitiveAcitivity (no "object").
|
||||||
|
case isIntransitiveActivity(typeName):
|
||||||
|
return activity, rawJSON, false
|
||||||
|
|
||||||
|
// Unknown.
|
||||||
|
default:
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ExtractPreferredUsername returns a string representation of
|
// ExtractPreferredUsername returns a string representation of
|
||||||
// an interface's preferredUsername property. Will return an
|
// an interface's preferredUsername property. Will return an
|
||||||
// error if preferredUsername is nil, not a string, or empty.
|
// error if preferredUsername is nil, not a string, or empty.
|
||||||
|
@ -497,6 +538,38 @@ func ExtractContent(i WithContent) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractAttachments attempts to extract barebones MediaAttachment objects from given AS interface type.
|
||||||
|
func ExtractAttachments(i WithAttachment) ([]*gtsmodel.MediaAttachment, error) {
|
||||||
|
attachmentProp := i.GetActivityStreamsAttachment()
|
||||||
|
if attachmentProp == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs gtserror.MultiError
|
||||||
|
|
||||||
|
attachments := make([]*gtsmodel.MediaAttachment, 0, attachmentProp.Len())
|
||||||
|
for iter := attachmentProp.Begin(); iter != attachmentProp.End(); iter = iter.Next() {
|
||||||
|
t := iter.GetType()
|
||||||
|
if t == nil {
|
||||||
|
errs.Appendf("nil attachment type")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
attachmentable, ok := t.(Attachmentable)
|
||||||
|
if !ok {
|
||||||
|
errs.Appendf("incorrect attachment type: %T", t)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
attachment, err := ExtractAttachment(attachmentable)
|
||||||
|
if err != nil {
|
||||||
|
errs.Appendf("error extracting attachment: %w", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
attachments = append(attachments, attachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachments, errs.Combine()
|
||||||
|
}
|
||||||
|
|
||||||
// ExtractAttachment extracts a minimal gtsmodel.Attachment
|
// ExtractAttachment extracts a minimal gtsmodel.Attachment
|
||||||
// (just remote URL, description, and blurhash) from the given
|
// (just remote URL, description, and blurhash) from the given
|
||||||
// Attachmentable interface, or an error if no remote URL is set.
|
// Attachmentable interface, or an error if no remote URL is set.
|
||||||
|
@ -913,6 +986,52 @@ func ExtractSharedInbox(withEndpoints WithEndpoints) *url.URL {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// nil check outside loop.
|
||||||
|
panic("nil function")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the one-of property from interface.
|
||||||
|
oneOfProp := withOneOf.GetActivityStreamsOneOf()
|
||||||
|
if oneOfProp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get start and end of iter.
|
||||||
|
start := oneOfProp.Begin()
|
||||||
|
end := oneOfProp.End()
|
||||||
|
|
||||||
|
// Pass iterated oneOf entries to given function.
|
||||||
|
for iter := start; iter != end; iter = iter.Next() {
|
||||||
|
foreach(iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateAnyOf will attempt to extract anyOf property from given interface, and passes each iterated item to function.
|
||||||
|
func IterateAnyOf(withAnyOf WithAnyOf, foreach func(vocab.ActivityStreamsAnyOfPropertyIterator)) {
|
||||||
|
if foreach == nil {
|
||||||
|
// nil check outside loop.
|
||||||
|
panic("nil function")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the any-of property from interface.
|
||||||
|
anyOfProp := withAnyOf.GetActivityStreamsAnyOf()
|
||||||
|
if anyOfProp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get start and end of iter.
|
||||||
|
start := anyOfProp.Begin()
|
||||||
|
end := anyOfProp.End()
|
||||||
|
|
||||||
|
// Pass iterated anyOf entries to given function.
|
||||||
|
for iter := start; iter != end; iter = iter.Next() {
|
||||||
|
foreach(iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// isPublic checks if at least one entry in the given
|
// isPublic checks if at least one entry in the given
|
||||||
// uris slice equals the activitystreams public uri.
|
// uris slice equals the activitystreams public uri.
|
||||||
func isPublic(uris []*url.URL) bool {
|
func isPublic(uris []*url.URL) bool {
|
||||||
|
|
|
@ -23,11 +23,76 @@
|
||||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsAccountable returns whether AS vocab type name is acceptable as Accountable.
|
||||||
|
func IsAccountable(typeName string) bool {
|
||||||
|
switch typeName {
|
||||||
|
case ActorPerson,
|
||||||
|
ActorApplication,
|
||||||
|
ActorOrganization,
|
||||||
|
ActorService,
|
||||||
|
ActorGroup:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToAccountable safely tries to cast vocab.Type as Accountable, also checking for expected AS type names.
|
||||||
|
func ToAccountable(t vocab.Type) (Accountable, bool) {
|
||||||
|
accountable, ok := t.(Accountable)
|
||||||
|
if !ok || !IsAccountable(t.GetTypeName()) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return accountable, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStatusable returns whether AS vocab type name is acceptable as Statusable.
|
||||||
|
func IsStatusable(typeName string) bool {
|
||||||
|
switch typeName {
|
||||||
|
case ObjectArticle,
|
||||||
|
ObjectDocument,
|
||||||
|
ObjectImage,
|
||||||
|
ObjectVideo,
|
||||||
|
ObjectNote,
|
||||||
|
ObjectPage,
|
||||||
|
ObjectEvent,
|
||||||
|
ObjectPlace,
|
||||||
|
ObjectProfile,
|
||||||
|
ActivityQuestion:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStatusable safely tries to cast vocab.Type as Statusable, also checking for expected AS type names.
|
||||||
|
func ToStatusable(t vocab.Type) (Statusable, bool) {
|
||||||
|
statusable, ok := t.(Statusable)
|
||||||
|
if !ok || !IsStatusable(t.GetTypeName()) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return statusable, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPollable returns whether AS vocab type name is acceptable as Pollable.
|
||||||
|
func IsPollable(typeName string) bool {
|
||||||
|
return typeName == ActivityQuestion
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToPollable safely tries to cast vocab.Type as Pollable, also checking for expected AS type names.
|
||||||
|
func ToPollable(t vocab.Type) (Pollable, bool) {
|
||||||
|
pollable, ok := t.(Pollable)
|
||||||
|
if !ok || !IsPollable(t.GetTypeName()) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return pollable, true
|
||||||
|
}
|
||||||
|
|
||||||
// Accountable represents the minimum activitypub interface for representing an 'account'.
|
// Accountable represents the minimum activitypub interface for representing an 'account'.
|
||||||
// This interface is fulfilled by: Person, Application, Organization, Service, and Group
|
// (see: IsAccountable() for types implementing this, though you MUST make sure to check
|
||||||
|
// the typeName as this bare interface may be implementable by non-Accountable types).
|
||||||
type Accountable interface {
|
type Accountable interface {
|
||||||
WithJSONLDId
|
vocab.Type
|
||||||
WithTypeName
|
|
||||||
|
|
||||||
WithPreferredUsername
|
WithPreferredUsername
|
||||||
WithIcon
|
WithIcon
|
||||||
|
@ -35,7 +100,6 @@ type Accountable interface {
|
||||||
WithImage
|
WithImage
|
||||||
WithSummary
|
WithSummary
|
||||||
WithAttachment
|
WithAttachment
|
||||||
WithSetSummary
|
|
||||||
WithDiscoverable
|
WithDiscoverable
|
||||||
WithURL
|
WithURL
|
||||||
WithPublicKey
|
WithPublicKey
|
||||||
|
@ -50,15 +114,13 @@ type Accountable interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statusable represents the minimum activitypub interface for representing a 'status'.
|
// Statusable represents the minimum activitypub interface for representing a 'status'.
|
||||||
// This interface is fulfilled by: Article, Document, Image, Video, Note, Page, Event, Place, Mention, Profile
|
// (see: IsStatusable() for types implementing this, though you MUST make sure to check
|
||||||
|
// the typeName as this bare interface may be implementable by non-Statusable types).
|
||||||
type Statusable interface {
|
type Statusable interface {
|
||||||
WithJSONLDId
|
vocab.Type
|
||||||
WithTypeName
|
|
||||||
|
|
||||||
WithSummary
|
WithSummary
|
||||||
WithSetSummary
|
|
||||||
WithName
|
WithName
|
||||||
WithSetName
|
|
||||||
WithInReplyTo
|
WithInReplyTo
|
||||||
WithPublished
|
WithPublished
|
||||||
WithURL
|
WithURL
|
||||||
|
@ -68,20 +130,40 @@ type Statusable interface {
|
||||||
WithSensitive
|
WithSensitive
|
||||||
WithConversation
|
WithConversation
|
||||||
WithContent
|
WithContent
|
||||||
WithSetContent
|
|
||||||
WithAttachment
|
WithAttachment
|
||||||
WithTag
|
WithTag
|
||||||
WithReplies
|
WithReplies
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'.
|
// Pollable represents the minimum activitypub interface for representing a 'poll' (it's a subset of a status).
|
||||||
|
// (see: IsPollable() for types implementing this, though you MUST make sure to check
|
||||||
|
// the typeName as this bare interface may be implementable by non-Pollable types).
|
||||||
|
type Pollable interface {
|
||||||
|
WithOneOf
|
||||||
|
WithAnyOf
|
||||||
|
WithEndTime
|
||||||
|
WithClosed
|
||||||
|
WithVotersCount
|
||||||
|
|
||||||
|
// base-interface
|
||||||
|
Statusable
|
||||||
|
}
|
||||||
|
|
||||||
|
// PollOptionable represents the minimum activitypub interface for representing a poll 'option'.
|
||||||
|
// (see: IsPollOptionable() for types implementing this).
|
||||||
|
type PollOptionable interface {
|
||||||
|
WithTypeName
|
||||||
|
WithName
|
||||||
|
WithReplies
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'. (see: IsAttachmentable).
|
||||||
// This interface is fulfilled by: Audio, Document, Image, Video
|
// This interface is fulfilled by: Audio, Document, Image, Video
|
||||||
type Attachmentable interface {
|
type Attachmentable interface {
|
||||||
WithTypeName
|
WithTypeName
|
||||||
WithMediaType
|
WithMediaType
|
||||||
WithURL
|
WithURL
|
||||||
WithName
|
WithName
|
||||||
WithSetName
|
|
||||||
WithBlurhash
|
WithBlurhash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,8 +242,7 @@ type ReplyToable interface {
|
||||||
// CollectionPageIterator represents the minimum interface for interacting with a wrapped
|
// CollectionPageIterator represents the minimum interface for interacting with a wrapped
|
||||||
// CollectionPage or OrderedCollectionPage in order to access both next / prev pages and items.
|
// CollectionPage or OrderedCollectionPage in order to access both next / prev pages and items.
|
||||||
type CollectionPageIterator interface {
|
type CollectionPageIterator interface {
|
||||||
WithJSONLDId
|
vocab.Type
|
||||||
WithTypeName
|
|
||||||
|
|
||||||
NextPage() WithIRI
|
NextPage() WithIRI
|
||||||
PrevPage() WithIRI
|
PrevPage() WithIRI
|
||||||
|
@ -189,12 +270,14 @@ type Flaggable interface {
|
||||||
// WithJSONLDId represents an activity with JSONLDIdProperty.
|
// WithJSONLDId represents an activity with JSONLDIdProperty.
|
||||||
type WithJSONLDId interface {
|
type WithJSONLDId interface {
|
||||||
GetJSONLDId() vocab.JSONLDIdProperty
|
GetJSONLDId() vocab.JSONLDIdProperty
|
||||||
|
SetJSONLDId(vocab.JSONLDIdProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithIRI represents an object (possibly) representable as an IRI.
|
// WithIRI represents an object (possibly) representable as an IRI.
|
||||||
type WithIRI interface {
|
type WithIRI interface {
|
||||||
GetIRI() *url.URL
|
GetIRI() *url.URL
|
||||||
IsIRI() bool
|
IsIRI() bool
|
||||||
|
SetIRI(*url.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithType ...
|
// WithType ...
|
||||||
|
@ -210,20 +293,18 @@ type WithTypeName interface {
|
||||||
// WithPreferredUsername represents an activity with ActivityStreamsPreferredUsernameProperty
|
// WithPreferredUsername represents an activity with ActivityStreamsPreferredUsernameProperty
|
||||||
type WithPreferredUsername interface {
|
type WithPreferredUsername interface {
|
||||||
GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty
|
GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty
|
||||||
|
SetActivityStreamsPreferredUsername(vocab.ActivityStreamsPreferredUsernameProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithIcon represents an activity with ActivityStreamsIconProperty
|
// WithIcon represents an activity with ActivityStreamsIconProperty
|
||||||
type WithIcon interface {
|
type WithIcon interface {
|
||||||
GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty
|
GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty
|
||||||
|
SetActivityStreamsIcon(vocab.ActivityStreamsIconProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithName represents an activity with ActivityStreamsNameProperty
|
// WithName represents an activity with ActivityStreamsNameProperty
|
||||||
type WithName interface {
|
type WithName interface {
|
||||||
GetActivityStreamsName() vocab.ActivityStreamsNameProperty
|
GetActivityStreamsName() vocab.ActivityStreamsNameProperty
|
||||||
}
|
|
||||||
|
|
||||||
// WithSetName represents an activity with a settable ActivityStreamsNameProperty
|
|
||||||
type WithSetName interface {
|
|
||||||
SetActivityStreamsName(vocab.ActivityStreamsNameProperty)
|
SetActivityStreamsName(vocab.ActivityStreamsNameProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,81 +316,91 @@ type WithImage interface {
|
||||||
// WithSummary represents an activity with ActivityStreamsSummaryProperty
|
// WithSummary represents an activity with ActivityStreamsSummaryProperty
|
||||||
type WithSummary interface {
|
type WithSummary interface {
|
||||||
GetActivityStreamsSummary() vocab.ActivityStreamsSummaryProperty
|
GetActivityStreamsSummary() vocab.ActivityStreamsSummaryProperty
|
||||||
}
|
|
||||||
|
|
||||||
// WithSetSummary represents an activity that can have summary set on it.
|
|
||||||
type WithSetSummary interface {
|
|
||||||
SetActivityStreamsSummary(vocab.ActivityStreamsSummaryProperty)
|
SetActivityStreamsSummary(vocab.ActivityStreamsSummaryProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithDiscoverable represents an activity with TootDiscoverableProperty
|
// WithDiscoverable represents an activity with TootDiscoverableProperty
|
||||||
type WithDiscoverable interface {
|
type WithDiscoverable interface {
|
||||||
GetTootDiscoverable() vocab.TootDiscoverableProperty
|
GetTootDiscoverable() vocab.TootDiscoverableProperty
|
||||||
|
SetTootDiscoverable(vocab.TootDiscoverableProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithURL represents an activity with ActivityStreamsUrlProperty
|
// WithURL represents an activity with ActivityStreamsUrlProperty
|
||||||
type WithURL interface {
|
type WithURL interface {
|
||||||
GetActivityStreamsUrl() vocab.ActivityStreamsUrlProperty
|
GetActivityStreamsUrl() vocab.ActivityStreamsUrlProperty
|
||||||
|
SetActivityStreamsUrl(vocab.ActivityStreamsUrlProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPublicKey represents an activity with W3IDSecurityV1PublicKeyProperty
|
// WithPublicKey represents an activity with W3IDSecurityV1PublicKeyProperty
|
||||||
type WithPublicKey interface {
|
type WithPublicKey interface {
|
||||||
GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty
|
GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty
|
||||||
|
SetW3IDSecurityV1PublicKey(vocab.W3IDSecurityV1PublicKeyProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithInbox represents an activity with ActivityStreamsInboxProperty
|
// WithInbox represents an activity with ActivityStreamsInboxProperty
|
||||||
type WithInbox interface {
|
type WithInbox interface {
|
||||||
GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty
|
GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty
|
||||||
|
SetActivityStreamsInbox(vocab.ActivityStreamsInboxProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithOutbox represents an activity with ActivityStreamsOutboxProperty
|
// WithOutbox represents an activity with ActivityStreamsOutboxProperty
|
||||||
type WithOutbox interface {
|
type WithOutbox interface {
|
||||||
GetActivityStreamsOutbox() vocab.ActivityStreamsOutboxProperty
|
GetActivityStreamsOutbox() vocab.ActivityStreamsOutboxProperty
|
||||||
|
SetActivityStreamsOutbox(vocab.ActivityStreamsOutboxProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFollowing represents an activity with ActivityStreamsFollowingProperty
|
// WithFollowing represents an activity with ActivityStreamsFollowingProperty
|
||||||
type WithFollowing interface {
|
type WithFollowing interface {
|
||||||
GetActivityStreamsFollowing() vocab.ActivityStreamsFollowingProperty
|
GetActivityStreamsFollowing() vocab.ActivityStreamsFollowingProperty
|
||||||
|
SetActivityStreamsFollowing(vocab.ActivityStreamsFollowingProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFollowers represents an activity with ActivityStreamsFollowersProperty
|
// WithFollowers represents an activity with ActivityStreamsFollowersProperty
|
||||||
type WithFollowers interface {
|
type WithFollowers interface {
|
||||||
GetActivityStreamsFollowers() vocab.ActivityStreamsFollowersProperty
|
GetActivityStreamsFollowers() vocab.ActivityStreamsFollowersProperty
|
||||||
|
SetActivityStreamsFollowers(vocab.ActivityStreamsFollowersProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFeatured represents an activity with TootFeaturedProperty
|
// WithFeatured represents an activity with TootFeaturedProperty
|
||||||
type WithFeatured interface {
|
type WithFeatured interface {
|
||||||
GetTootFeatured() vocab.TootFeaturedProperty
|
GetTootFeatured() vocab.TootFeaturedProperty
|
||||||
|
SetTootFeatured(vocab.TootFeaturedProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithAttributedTo represents an activity with ActivityStreamsAttributedToProperty
|
// WithAttributedTo represents an activity with ActivityStreamsAttributedToProperty
|
||||||
type WithAttributedTo interface {
|
type WithAttributedTo interface {
|
||||||
GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty
|
GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty
|
||||||
|
SetActivityStreamsAttributedTo(vocab.ActivityStreamsAttributedToProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithAttachment represents an activity with ActivityStreamsAttachmentProperty
|
// WithAttachment represents an activity with ActivityStreamsAttachmentProperty
|
||||||
type WithAttachment interface {
|
type WithAttachment interface {
|
||||||
GetActivityStreamsAttachment() vocab.ActivityStreamsAttachmentProperty
|
GetActivityStreamsAttachment() vocab.ActivityStreamsAttachmentProperty
|
||||||
|
SetActivityStreamsAttachment(vocab.ActivityStreamsAttachmentProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTo represents an activity with ActivityStreamsToProperty
|
// WithTo represents an activity with ActivityStreamsToProperty
|
||||||
type WithTo interface {
|
type WithTo interface {
|
||||||
GetActivityStreamsTo() vocab.ActivityStreamsToProperty
|
GetActivityStreamsTo() vocab.ActivityStreamsToProperty
|
||||||
|
SetActivityStreamsTo(vocab.ActivityStreamsToProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithInReplyTo represents an activity with ActivityStreamsInReplyToProperty
|
// WithInReplyTo represents an activity with ActivityStreamsInReplyToProperty
|
||||||
type WithInReplyTo interface {
|
type WithInReplyTo interface {
|
||||||
GetActivityStreamsInReplyTo() vocab.ActivityStreamsInReplyToProperty
|
GetActivityStreamsInReplyTo() vocab.ActivityStreamsInReplyToProperty
|
||||||
|
SetActivityStreamsInReplyTo(vocab.ActivityStreamsInReplyToProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCC represents an activity with ActivityStreamsCcProperty
|
// WithCC represents an activity with ActivityStreamsCcProperty
|
||||||
type WithCC interface {
|
type WithCC interface {
|
||||||
GetActivityStreamsCc() vocab.ActivityStreamsCcProperty
|
GetActivityStreamsCc() vocab.ActivityStreamsCcProperty
|
||||||
|
SetActivityStreamsCc(vocab.ActivityStreamsCcProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSensitive represents an activity with ActivityStreamsSensitiveProperty
|
// WithSensitive represents an activity with ActivityStreamsSensitiveProperty
|
||||||
type WithSensitive interface {
|
type WithSensitive interface {
|
||||||
GetActivityStreamsSensitive() vocab.ActivityStreamsSensitiveProperty
|
GetActivityStreamsSensitive() vocab.ActivityStreamsSensitiveProperty
|
||||||
|
SetActivityStreamsSensitive(vocab.ActivityStreamsSensitiveProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithConversation ...
|
// WithConversation ...
|
||||||
|
@ -319,36 +410,37 @@ type WithConversation interface {
|
||||||
// WithContent represents an activity with ActivityStreamsContentProperty
|
// WithContent represents an activity with ActivityStreamsContentProperty
|
||||||
type WithContent interface {
|
type WithContent interface {
|
||||||
GetActivityStreamsContent() vocab.ActivityStreamsContentProperty
|
GetActivityStreamsContent() vocab.ActivityStreamsContentProperty
|
||||||
}
|
|
||||||
|
|
||||||
// WithSetContent represents an activity that can have content set on it.
|
|
||||||
type WithSetContent interface {
|
|
||||||
SetActivityStreamsContent(vocab.ActivityStreamsContentProperty)
|
SetActivityStreamsContent(vocab.ActivityStreamsContentProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPublished represents an activity with ActivityStreamsPublishedProperty
|
// WithPublished represents an activity with ActivityStreamsPublishedProperty
|
||||||
type WithPublished interface {
|
type WithPublished interface {
|
||||||
GetActivityStreamsPublished() vocab.ActivityStreamsPublishedProperty
|
GetActivityStreamsPublished() vocab.ActivityStreamsPublishedProperty
|
||||||
|
SetActivityStreamsPublished(vocab.ActivityStreamsPublishedProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTag represents an activity with ActivityStreamsTagProperty
|
// WithTag represents an activity with ActivityStreamsTagProperty
|
||||||
type WithTag interface {
|
type WithTag interface {
|
||||||
GetActivityStreamsTag() vocab.ActivityStreamsTagProperty
|
GetActivityStreamsTag() vocab.ActivityStreamsTagProperty
|
||||||
|
SetActivityStreamsTag(vocab.ActivityStreamsTagProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithReplies represents an activity with ActivityStreamsRepliesProperty
|
// WithReplies represents an activity with ActivityStreamsRepliesProperty
|
||||||
type WithReplies interface {
|
type WithReplies interface {
|
||||||
GetActivityStreamsReplies() vocab.ActivityStreamsRepliesProperty
|
GetActivityStreamsReplies() vocab.ActivityStreamsRepliesProperty
|
||||||
|
SetActivityStreamsReplies(vocab.ActivityStreamsRepliesProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithMediaType represents an activity with ActivityStreamsMediaTypeProperty
|
// WithMediaType represents an activity with ActivityStreamsMediaTypeProperty
|
||||||
type WithMediaType interface {
|
type WithMediaType interface {
|
||||||
GetActivityStreamsMediaType() vocab.ActivityStreamsMediaTypeProperty
|
GetActivityStreamsMediaType() vocab.ActivityStreamsMediaTypeProperty
|
||||||
|
SetActivityStreamsMediaType(vocab.ActivityStreamsMediaTypeProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithBlurhash represents an activity with TootBlurhashProperty
|
// WithBlurhash represents an activity with TootBlurhashProperty
|
||||||
type WithBlurhash interface {
|
type WithBlurhash interface {
|
||||||
GetTootBlurhash() vocab.TootBlurhashProperty
|
GetTootBlurhash() vocab.TootBlurhashProperty
|
||||||
|
SetTootBlurhash(vocab.TootBlurhashProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// type withFocalPoint interface {
|
// type withFocalPoint interface {
|
||||||
|
@ -358,44 +450,83 @@ type WithBlurhash interface {
|
||||||
// WithHref represents an activity with ActivityStreamsHrefProperty
|
// WithHref represents an activity with ActivityStreamsHrefProperty
|
||||||
type WithHref interface {
|
type WithHref interface {
|
||||||
GetActivityStreamsHref() vocab.ActivityStreamsHrefProperty
|
GetActivityStreamsHref() vocab.ActivityStreamsHrefProperty
|
||||||
|
SetActivityStreamsHref(vocab.ActivityStreamsHrefProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithUpdated represents an activity with ActivityStreamsUpdatedProperty
|
// WithUpdated represents an activity with ActivityStreamsUpdatedProperty
|
||||||
type WithUpdated interface {
|
type WithUpdated interface {
|
||||||
GetActivityStreamsUpdated() vocab.ActivityStreamsUpdatedProperty
|
GetActivityStreamsUpdated() vocab.ActivityStreamsUpdatedProperty
|
||||||
|
SetActivityStreamsUpdated(vocab.ActivityStreamsUpdatedProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithActor represents an activity with ActivityStreamsActorProperty
|
// WithActor represents an activity with ActivityStreamsActorProperty
|
||||||
type WithActor interface {
|
type WithActor interface {
|
||||||
GetActivityStreamsActor() vocab.ActivityStreamsActorProperty
|
GetActivityStreamsActor() vocab.ActivityStreamsActorProperty
|
||||||
|
SetActivityStreamsActor(vocab.ActivityStreamsActorProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithObject represents an activity with ActivityStreamsObjectProperty
|
// WithObject represents an activity with ActivityStreamsObjectProperty
|
||||||
type WithObject interface {
|
type WithObject interface {
|
||||||
GetActivityStreamsObject() vocab.ActivityStreamsObjectProperty
|
GetActivityStreamsObject() vocab.ActivityStreamsObjectProperty
|
||||||
|
SetActivityStreamsObject(vocab.ActivityStreamsObjectProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithNext represents an activity with ActivityStreamsNextProperty
|
// WithNext represents an activity with ActivityStreamsNextProperty
|
||||||
type WithNext interface {
|
type WithNext interface {
|
||||||
GetActivityStreamsNext() vocab.ActivityStreamsNextProperty
|
GetActivityStreamsNext() vocab.ActivityStreamsNextProperty
|
||||||
|
SetActivityStreamsNext(vocab.ActivityStreamsNextProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPartOf represents an activity with ActivityStreamsPartOfProperty
|
// WithPartOf represents an activity with ActivityStreamsPartOfProperty
|
||||||
type WithPartOf interface {
|
type WithPartOf interface {
|
||||||
GetActivityStreamsPartOf() vocab.ActivityStreamsPartOfProperty
|
GetActivityStreamsPartOf() vocab.ActivityStreamsPartOfProperty
|
||||||
|
SetActivityStreamsPartOf(vocab.ActivityStreamsPartOfProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithItems represents an activity with ActivityStreamsItemsProperty
|
// WithItems represents an activity with ActivityStreamsItemsProperty
|
||||||
type WithItems interface {
|
type WithItems interface {
|
||||||
GetActivityStreamsItems() vocab.ActivityStreamsItemsProperty
|
GetActivityStreamsItems() vocab.ActivityStreamsItemsProperty
|
||||||
|
SetActivityStreamsItems(vocab.ActivityStreamsItemsProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithManuallyApprovesFollowers represents a Person or profile with the ManuallyApprovesFollowers property.
|
// WithManuallyApprovesFollowers represents a Person or profile with the ManuallyApprovesFollowers property.
|
||||||
type WithManuallyApprovesFollowers interface {
|
type WithManuallyApprovesFollowers interface {
|
||||||
GetActivityStreamsManuallyApprovesFollowers() vocab.ActivityStreamsManuallyApprovesFollowersProperty
|
GetActivityStreamsManuallyApprovesFollowers() vocab.ActivityStreamsManuallyApprovesFollowersProperty
|
||||||
|
SetActivityStreamsManuallyApprovesFollowers(vocab.ActivityStreamsManuallyApprovesFollowersProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithEndpoints represents a Person or profile with the endpoints property
|
// WithEndpoints represents a Person or profile with the endpoints property
|
||||||
type WithEndpoints interface {
|
type WithEndpoints interface {
|
||||||
GetActivityStreamsEndpoints() vocab.ActivityStreamsEndpointsProperty
|
GetActivityStreamsEndpoints() vocab.ActivityStreamsEndpointsProperty
|
||||||
|
SetActivityStreamsEndpoints(vocab.ActivityStreamsEndpointsProperty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOneOf represents an activity with the oneOf property.
|
||||||
|
type WithOneOf interface {
|
||||||
|
GetActivityStreamsOneOf() vocab.ActivityStreamsOneOfProperty
|
||||||
|
SetActivityStreamsOneOf(vocab.ActivityStreamsOneOfProperty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOneOf represents an activity with the oneOf property.
|
||||||
|
type WithAnyOf interface {
|
||||||
|
GetActivityStreamsAnyOf() vocab.ActivityStreamsAnyOfProperty
|
||||||
|
SetActivityStreamsAnyOf(vocab.ActivityStreamsAnyOfProperty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEndTime represents an activity with the endTime property.
|
||||||
|
type WithEndTime interface {
|
||||||
|
GetActivityStreamsEndTime() vocab.ActivityStreamsEndTimeProperty
|
||||||
|
SetActivityStreamsEndTime(vocab.ActivityStreamsEndTimeProperty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithClosed represents an activity with the closed property.
|
||||||
|
type WithClosed interface {
|
||||||
|
GetActivityStreamsClosed() vocab.ActivityStreamsClosedProperty
|
||||||
|
SetActivityStreamsClosed(vocab.ActivityStreamsClosedProperty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithVotersCount represents an activity with the votersCount property.
|
||||||
|
type WithVotersCount interface {
|
||||||
|
GetTootVotersCount() vocab.TootVotersCountProperty
|
||||||
|
SetTootVotersCount(vocab.TootVotersCountProperty)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,92 +37,62 @@
|
||||||
// The rawActivity map should the freshly deserialized json representation of the Activity.
|
// The rawActivity map should the freshly deserialized json representation of the Activity.
|
||||||
//
|
//
|
||||||
// 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.
|
// 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 NormalizeIncomingActivityObject(activity pub.Activity, rawJSON map[string]interface{}) {
|
func NormalizeIncomingActivity(activity pub.Activity, rawJSON map[string]interface{}) {
|
||||||
if typeName := activity.GetTypeName(); typeName != ActivityCreate && typeName != ActivityUpdate {
|
// From the activity extract the data vocab.Type + its "raw" JSON.
|
||||||
// Only interested in Create or Update right now.
|
dataType, rawData, ok := ExtractActivityData(activity, rawJSON)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
withObject, ok := activity.(WithObject)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// Create was not a WithObject.
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createObject := withObject.GetActivityStreamsObject()
|
switch dataType.GetTypeName() {
|
||||||
if createObject == nil {
|
// "Pollable" types.
|
||||||
// No object set.
|
case ActivityQuestion:
|
||||||
return
|
pollable, ok := dataType.(Pollable)
|
||||||
}
|
|
||||||
|
|
||||||
if createObject.Len() != 1 {
|
|
||||||
// Not interested in Object arrays.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We now know length is 1 so get the first
|
|
||||||
// item from the iter. We need this to be
|
|
||||||
// a Statusable or Accountable if we're to continue.
|
|
||||||
i := createObject.At(0)
|
|
||||||
if i == nil {
|
|
||||||
// This is awkward.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t := i.GetType()
|
|
||||||
if t == nil {
|
|
||||||
// This is also awkward.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t.GetTypeName() {
|
|
||||||
case ObjectArticle, ObjectDocument, ObjectImage, ObjectVideo, ObjectNote, ObjectPage, ObjectEvent, ObjectPlace, ObjectProfile:
|
|
||||||
statusable, ok := t.(Statusable)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// Object is not Statusable;
|
|
||||||
// we're not interested.
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rawObject, ok := rawJSON["object"]
|
// Normalize the Pollable specific properties.
|
||||||
if !ok {
|
NormalizeIncomingPollOptions(pollable, rawData)
|
||||||
// No object in raw map.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rawStatusableJSON, ok := rawObject.(map[string]interface{})
|
// Fallthrough to handle
|
||||||
|
// the rest as Statusable.
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
// "Statusable" types.
|
||||||
|
case ObjectArticle,
|
||||||
|
ObjectDocument,
|
||||||
|
ObjectImage,
|
||||||
|
ObjectVideo,
|
||||||
|
ObjectNote,
|
||||||
|
ObjectPage,
|
||||||
|
ObjectEvent,
|
||||||
|
ObjectPlace,
|
||||||
|
ObjectProfile:
|
||||||
|
statusable, ok := dataType.(Statusable)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Object wasn't a json object.
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize everything we can on the statusable.
|
// Normalize everything we can on the statusable.
|
||||||
NormalizeIncomingContent(statusable, rawStatusableJSON)
|
NormalizeIncomingContent(statusable, rawData)
|
||||||
NormalizeIncomingAttachments(statusable, rawStatusableJSON)
|
NormalizeIncomingAttachments(statusable, rawData)
|
||||||
NormalizeIncomingSummary(statusable, rawStatusableJSON)
|
NormalizeIncomingSummary(statusable, rawData)
|
||||||
NormalizeIncomingName(statusable, rawStatusableJSON)
|
NormalizeIncomingName(statusable, rawData)
|
||||||
case ActorApplication, ActorGroup, ActorOrganization, ActorPerson, ActorService:
|
|
||||||
accountable, ok := t.(Accountable)
|
|
||||||
if !ok {
|
|
||||||
// Object is not Accountable;
|
|
||||||
// we're not interested.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rawObject, ok := rawJSON["object"]
|
// "Accountable" types.
|
||||||
|
case ActorApplication,
|
||||||
|
ActorGroup,
|
||||||
|
ActorOrganization,
|
||||||
|
ActorPerson,
|
||||||
|
ActorService:
|
||||||
|
accountable, ok := dataType.(Accountable)
|
||||||
if !ok {
|
if !ok {
|
||||||
// No object in raw map.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rawAccountableJSON, ok := rawObject.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
// Object wasn't a json object.
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize everything we can on the accountable.
|
// Normalize everything we can on the accountable.
|
||||||
NormalizeIncomingSummary(accountable, rawAccountableJSON)
|
NormalizeIncomingSummary(accountable, rawData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +102,7 @@ func NormalizeIncomingActivityObject(activity pub.Activity, rawJSON map[string]i
|
||||||
//
|
//
|
||||||
// noop if there was no content in the json object map or the
|
// noop if there was no content in the json object map or the
|
||||||
// content was not a plain string.
|
// content was not a plain string.
|
||||||
func NormalizeIncomingContent(item WithSetContent, rawJSON map[string]interface{}) {
|
func NormalizeIncomingContent(item WithContent, rawJSON map[string]interface{}) {
|
||||||
rawContent, ok := rawJSON["content"]
|
rawContent, ok := rawJSON["content"]
|
||||||
if !ok {
|
if !ok {
|
||||||
// No content in rawJSON.
|
// No content in rawJSON.
|
||||||
|
@ -228,7 +198,7 @@ func NormalizeIncomingAttachments(item WithAttachment, rawJSON map[string]interf
|
||||||
//
|
//
|
||||||
// noop if there was no summary in the json object map or the
|
// noop if there was no summary in the json object map or the
|
||||||
// summary was not a plain string.
|
// summary was not a plain string.
|
||||||
func NormalizeIncomingSummary(item WithSetSummary, rawJSON map[string]interface{}) {
|
func NormalizeIncomingSummary(item WithSummary, rawJSON map[string]interface{}) {
|
||||||
rawSummary, ok := rawJSON["summary"]
|
rawSummary, ok := rawJSON["summary"]
|
||||||
if !ok {
|
if !ok {
|
||||||
// No summary in rawJSON.
|
// No summary in rawJSON.
|
||||||
|
@ -258,7 +228,7 @@ func NormalizeIncomingSummary(item WithSetSummary, rawJSON map[string]interface{
|
||||||
//
|
//
|
||||||
// noop if there was no name in the json object map or the
|
// noop if there was no name in the json object map or the
|
||||||
// name was not a plain string.
|
// name was not a plain string.
|
||||||
func NormalizeIncomingName(item WithSetName, rawJSON map[string]interface{}) {
|
func NormalizeIncomingName(item WithName, rawJSON map[string]interface{}) {
|
||||||
rawName, ok := rawJSON["name"]
|
rawName, ok := rawJSON["name"]
|
||||||
if !ok {
|
if !ok {
|
||||||
// No name in rawJSON.
|
// No name in rawJSON.
|
||||||
|
@ -284,3 +254,60 @@ func NormalizeIncomingName(item WithSetName, rawJSON map[string]interface{}) {
|
||||||
nameProp.AppendXMLSchemaString(name)
|
nameProp.AppendXMLSchemaString(name)
|
||||||
item.SetActivityStreamsName(nameProp)
|
item.SetActivityStreamsName(nameProp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NormalizeIncomingOneOf normalizes all oneOf (if any) of the given
|
||||||
|
// item, replacing the 'name' field of each oneOf with the raw 'name'
|
||||||
|
// value from the raw json object map, and doing sanitization
|
||||||
|
// on the result.
|
||||||
|
//
|
||||||
|
// noop if there are no oneOf; noop if oneOf is not expected format.
|
||||||
|
func NormalizeIncomingPollOptions(item WithOneOf, rawJSON map[string]interface{}) {
|
||||||
|
var oneOf []interface{}
|
||||||
|
|
||||||
|
// Get the raw one-of JSON data.
|
||||||
|
rawOneOf, ok := rawJSON["oneOf"]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to slice if not already, so we can iterate.
|
||||||
|
if oneOf, ok = rawOneOf.([]interface{}); !ok {
|
||||||
|
oneOf = []interface{}{rawOneOf}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the one-of property from interface.
|
||||||
|
oneOfProp := item.GetActivityStreamsOneOf()
|
||||||
|
if oneOfProp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check we have useable one-of JSON-vs-unmarshaled data.
|
||||||
|
if l := oneOfProp.Len(); l == 0 || l != len(oneOf) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get start and end of iter.
|
||||||
|
start := oneOfProp.Begin()
|
||||||
|
end := oneOfProp.End()
|
||||||
|
|
||||||
|
// Iterate a counter, from start through to end iter item.
|
||||||
|
for i, iter := 0, start; iter != end; i, iter = i+1, iter.Next() {
|
||||||
|
// Get item type.
|
||||||
|
t := iter.GetType()
|
||||||
|
|
||||||
|
// Check fulfills Choiceable type
|
||||||
|
// (this accounts for nil input type).
|
||||||
|
choiceable, ok := t.(PollOptionable)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the corresponding raw one-of data.
|
||||||
|
rawChoice, ok := oneOf[i].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
NormalizeIncomingName(choiceable, rawChoice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -191,7 +191,7 @@ func (suite *NormalizeTestSuite) TestNormalizeActivityObject() {
|
||||||
note,
|
note,
|
||||||
)
|
)
|
||||||
|
|
||||||
ap.NormalizeIncomingActivityObject(create, map[string]interface{}{"object": rawNote})
|
ap.NormalizeIncomingActivity(create, map[string]interface{}{"object": rawNote})
|
||||||
suite.Equal(`UPDATE: As of this morning there are now more than 7 million Mastodon users, most from the <a class="hashtag" href="https://example.org/tag/twittermigration" rel="tag ugc nofollow noreferrer noopener" target="_blank">#TwitterMigration</a>.<br><br>In fact, 100,000 new accounts have been created since last night.<br><br>Since last night's spike 8,000-12,000 new accounts are being created every hour.<br><br>Yesterday, I estimated that Mastodon would have 8 million users by the end of the week. That might happen a lot sooner if this trend continues.`, ap.ExtractContent(note))
|
suite.Equal(`UPDATE: As of this morning there are now more than 7 million Mastodon users, most from the <a class="hashtag" href="https://example.org/tag/twittermigration" rel="tag ugc nofollow noreferrer noopener" target="_blank">#TwitterMigration</a>.<br><br>In fact, 100,000 new accounts have been created since last night.<br><br>Since last night's spike 8,000-12,000 new accounts are being created every hour.<br><br>Yesterday, I estimated that Mastodon would have 8 million users by the end of the week. That might happen a lot sooner if this trend continues.`, ap.ExtractContent(note))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,62 +20,134 @@
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/superseriousbusiness/activity/pub"
|
||||||
"github.com/superseriousbusiness/activity/streams"
|
"github.com/superseriousbusiness/activity/streams"
|
||||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// mapPool is a memory pool of maps for JSON decoding.
|
||||||
|
var mapPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return make(map[string]any)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMap acquires a map from memory pool.
|
||||||
|
func getMap() map[string]any {
|
||||||
|
m := mapPool.Get().(map[string]any) //nolint
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// putMap clears and places map back in pool.
|
||||||
|
func putMap(m map[string]any) {
|
||||||
|
if len(m) > int(^uint8(0)) {
|
||||||
|
// don't pool overly
|
||||||
|
// large maps.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k := range m {
|
||||||
|
delete(m, k)
|
||||||
|
}
|
||||||
|
mapPool.Put(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveActivity is a util function for pulling a pub.Activity type out of an incoming request body.
|
||||||
|
func ResolveIncomingActivity(r *http.Request) (pub.Activity, gtserror.WithCode) {
|
||||||
|
// Get "raw" map
|
||||||
|
// destination.
|
||||||
|
raw := getMap()
|
||||||
|
|
||||||
|
// Tidy up when done.
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
// Decode the JSON body stream into "raw" map.
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&raw); err != nil {
|
||||||
|
err := gtserror.Newf("error decoding json: %w", err)
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve "raw" JSON to vocab.Type.
|
||||||
|
t, err := streams.ToType(r.Context(), raw)
|
||||||
|
if err != nil {
|
||||||
|
if !streams.IsUnmatchedErr(err) {
|
||||||
|
err := gtserror.Newf("error matching json to type: %w", err)
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond with bad request; we just couldn't
|
||||||
|
// match the type to one that we know about.
|
||||||
|
const text = "body json not resolvable as ActivityStreams type"
|
||||||
|
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure this is an Activity type.
|
||||||
|
activity, ok := t.(pub.Activity)
|
||||||
|
if !ok {
|
||||||
|
text := fmt.Sprintf("cannot resolve vocab type %T as pub.Activity", t)
|
||||||
|
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if activity.GetJSONLDId() == nil {
|
||||||
|
const text = "missing ActivityStreams id property"
|
||||||
|
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize any Statusable, Accountable, Pollable fields found.
|
||||||
|
// (see: https://github.com/superseriousbusiness/gotosocial/issues/1661)
|
||||||
|
NormalizeIncomingActivity(activity, raw)
|
||||||
|
|
||||||
|
// Release.
|
||||||
|
putMap(raw)
|
||||||
|
|
||||||
|
return activity, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ResolveStatusable tries to resolve the given bytes into an ActivityPub Statusable representation.
|
// ResolveStatusable tries to resolve the given bytes into an ActivityPub Statusable representation.
|
||||||
// It will then perform normalization on the Statusable.
|
// It will then perform normalization on the Statusable.
|
||||||
//
|
//
|
||||||
// Works for: Article, Document, Image, Video, Note, Page, Event, Place, Profile
|
// Works for: Article, Document, Image, Video, Note, Page, Event, Place, Profile, Question.
|
||||||
func ResolveStatusable(ctx context.Context, b []byte) (Statusable, error) {
|
func ResolveStatusable(ctx context.Context, b []byte) (Statusable, error) {
|
||||||
rawStatusable := make(map[string]interface{})
|
// Get "raw" map
|
||||||
if err := json.Unmarshal(b, &rawStatusable); err != nil {
|
// destination.
|
||||||
|
raw := getMap()
|
||||||
|
|
||||||
|
// Unmarshal the raw JSON data in a "raw" JSON map.
|
||||||
|
if err := json.Unmarshal(b, &raw); err != nil {
|
||||||
return nil, gtserror.Newf("error unmarshalling bytes into json: %w", err)
|
return nil, gtserror.Newf("error unmarshalling bytes into json: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := streams.ToType(ctx, rawStatusable)
|
// Resolve an ActivityStreams type from JSON.
|
||||||
|
t, err := streams.ToType(ctx, raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.Newf("error resolving json into ap vocab type: %w", err)
|
return nil, gtserror.Newf("error resolving json into ap vocab type: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// Attempt to cast as Statusable.
|
||||||
statusable Statusable
|
statusable, ok := ToStatusable(t)
|
||||||
ok bool
|
|
||||||
)
|
|
||||||
|
|
||||||
switch t.GetTypeName() {
|
|
||||||
case ObjectArticle:
|
|
||||||
statusable, ok = t.(vocab.ActivityStreamsArticle)
|
|
||||||
case ObjectDocument:
|
|
||||||
statusable, ok = t.(vocab.ActivityStreamsDocument)
|
|
||||||
case ObjectImage:
|
|
||||||
statusable, ok = t.(vocab.ActivityStreamsImage)
|
|
||||||
case ObjectVideo:
|
|
||||||
statusable, ok = t.(vocab.ActivityStreamsVideo)
|
|
||||||
case ObjectNote:
|
|
||||||
statusable, ok = t.(vocab.ActivityStreamsNote)
|
|
||||||
case ObjectPage:
|
|
||||||
statusable, ok = t.(vocab.ActivityStreamsPage)
|
|
||||||
case ObjectEvent:
|
|
||||||
statusable, ok = t.(vocab.ActivityStreamsEvent)
|
|
||||||
case ObjectPlace:
|
|
||||||
statusable, ok = t.(vocab.ActivityStreamsPlace)
|
|
||||||
case ObjectProfile:
|
|
||||||
statusable, ok = t.(vocab.ActivityStreamsProfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
err = gtserror.Newf("could not resolve %T to Statusable", t)
|
err := gtserror.Newf("cannot resolve vocab type %T as statusable", t)
|
||||||
return nil, gtserror.SetWrongType(err)
|
return nil, gtserror.SetWrongType(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
NormalizeIncomingContent(statusable, rawStatusable)
|
if pollable, ok := ToPollable(statusable); ok {
|
||||||
NormalizeIncomingAttachments(statusable, rawStatusable)
|
// Question requires extra normalization, and
|
||||||
NormalizeIncomingSummary(statusable, rawStatusable)
|
// fortunately directly implements Statusable.
|
||||||
NormalizeIncomingName(statusable, rawStatusable)
|
NormalizeIncomingPollOptions(pollable, raw)
|
||||||
|
statusable = pollable
|
||||||
|
}
|
||||||
|
|
||||||
|
NormalizeIncomingContent(statusable, raw)
|
||||||
|
NormalizeIncomingAttachments(statusable, raw)
|
||||||
|
NormalizeIncomingSummary(statusable, raw)
|
||||||
|
NormalizeIncomingName(statusable, raw)
|
||||||
|
|
||||||
|
// Release.
|
||||||
|
putMap(raw)
|
||||||
|
|
||||||
return statusable, nil
|
return statusable, nil
|
||||||
}
|
}
|
||||||
|
@ -85,40 +157,32 @@ func ResolveStatusable(ctx context.Context, b []byte) (Statusable, error) {
|
||||||
//
|
//
|
||||||
// Works for: Application, Group, Organization, Person, Service
|
// Works for: Application, Group, Organization, Person, Service
|
||||||
func ResolveAccountable(ctx context.Context, b []byte) (Accountable, error) {
|
func ResolveAccountable(ctx context.Context, b []byte) (Accountable, error) {
|
||||||
rawAccountable := make(map[string]interface{})
|
// Get "raw" map
|
||||||
if err := json.Unmarshal(b, &rawAccountable); err != nil {
|
// destination.
|
||||||
|
raw := getMap()
|
||||||
|
|
||||||
|
// Unmarshal the raw JSON data in a "raw" JSON map.
|
||||||
|
if err := json.Unmarshal(b, &raw); err != nil {
|
||||||
return nil, gtserror.Newf("error unmarshalling bytes into json: %w", err)
|
return nil, gtserror.Newf("error unmarshalling bytes into json: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := streams.ToType(ctx, rawAccountable)
|
// Resolve an ActivityStreams type from JSON.
|
||||||
|
t, err := streams.ToType(ctx, raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.Newf("error resolving json into ap vocab type: %w", err)
|
return nil, gtserror.Newf("error resolving json into ap vocab type: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// Attempt to cast as Statusable.
|
||||||
accountable Accountable
|
accountable, ok := ToAccountable(t)
|
||||||
ok bool
|
|
||||||
)
|
|
||||||
|
|
||||||
switch t.GetTypeName() {
|
|
||||||
case ActorApplication:
|
|
||||||
accountable, ok = t.(vocab.ActivityStreamsApplication)
|
|
||||||
case ActorGroup:
|
|
||||||
accountable, ok = t.(vocab.ActivityStreamsGroup)
|
|
||||||
case ActorOrganization:
|
|
||||||
accountable, ok = t.(vocab.ActivityStreamsOrganization)
|
|
||||||
case ActorPerson:
|
|
||||||
accountable, ok = t.(vocab.ActivityStreamsPerson)
|
|
||||||
case ActorService:
|
|
||||||
accountable, ok = t.(vocab.ActivityStreamsService)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
err = gtserror.Newf("could not resolve %T to Accountable", t)
|
err := gtserror.Newf("cannot resolve vocab type %T as accountable", t)
|
||||||
return nil, gtserror.SetWrongType(err)
|
return nil, gtserror.SetWrongType(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
NormalizeIncomingSummary(accountable, rawAccountable)
|
NormalizeIncomingSummary(accountable, raw)
|
||||||
|
|
||||||
|
// Release.
|
||||||
|
putMap(raw)
|
||||||
|
|
||||||
return accountable, nil
|
return accountable, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ func (suite *ResolveTestSuite) TestResolveDocumentAsAccountable() {
|
||||||
|
|
||||||
accountable, err := ap.ResolveAccountable(context.Background(), b)
|
accountable, err := ap.ResolveAccountable(context.Background(), b)
|
||||||
suite.True(gtserror.WrongType(err))
|
suite.True(gtserror.WrongType(err))
|
||||||
suite.EqualError(err, "ResolveAccountable: could not resolve *typedocument.ActivityStreamsDocument to Accountable")
|
suite.EqualError(err, "ResolveAccountable: cannot resolve vocab type *typedocument.ActivityStreamsDocument as accountable")
|
||||||
suite.Nil(accountable)
|
suite.Nil(accountable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -486,7 +486,7 @@ func (suite *InboxPostTestSuite) TestPostEmptyCreate() {
|
||||||
requestingAccount,
|
requestingAccount,
|
||||||
targetAccount,
|
targetAccount,
|
||||||
http.StatusBadRequest,
|
http.StatusBadRequest,
|
||||||
`{"error":"Bad Request: incoming Activity Create did not have required id property set"}`,
|
`{"error":"Bad Request: missing ActivityStreams id property"}`,
|
||||||
suite.signatureCheck,
|
suite.signatureCheck,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -511,7 +511,7 @@ func (suite *InboxPostTestSuite) TestPostFromBlockedAccount() {
|
||||||
requestingAccount,
|
requestingAccount,
|
||||||
targetAccount,
|
targetAccount,
|
||||||
http.StatusForbidden,
|
http.StatusForbidden,
|
||||||
`{"error":"Forbidden"}`,
|
`{"error":"Forbidden: blocked"}`,
|
||||||
suite.signatureCheck,
|
suite.signatureCheck,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -555,7 +555,7 @@ func (suite *InboxPostTestSuite) TestPostUnauthorized() {
|
||||||
requestingAccount,
|
requestingAccount,
|
||||||
targetAccount,
|
targetAccount,
|
||||||
http.StatusUnauthorized,
|
http.StatusUnauthorized,
|
||||||
`{"error":"Unauthorized"}`,
|
`{"error":"Unauthorized: not authenticated"}`,
|
||||||
// Omit signature check middleware.
|
// Omit signature check middleware.
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,8 @@
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -30,7 +28,6 @@
|
||||||
errorsv2 "codeberg.org/gruf/go-errors/v2"
|
errorsv2 "codeberg.org/gruf/go-errors/v2"
|
||||||
"codeberg.org/gruf/go-kv"
|
"codeberg.org/gruf/go-kv"
|
||||||
"github.com/superseriousbusiness/activity/pub"
|
"github.com/superseriousbusiness/activity/pub"
|
||||||
"github.com/superseriousbusiness/activity/streams"
|
|
||||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
@ -132,12 +129,13 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
|
||||||
// Authenticate request by checking http signature.
|
// Authenticate request by checking http signature.
|
||||||
ctx, authenticated, err := f.sideEffectActor.AuthenticatePostInbox(ctx, w, r)
|
ctx, authenticated, err := f.sideEffectActor.AuthenticatePostInbox(ctx, w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
err := gtserror.Newf("error authenticating post inbox: %w", err)
|
||||||
return false, gtserror.NewErrorInternalError(err)
|
return false, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authenticated {
|
if !authenticated {
|
||||||
err = errors.New("not authenticated")
|
const text = "not authenticated"
|
||||||
return false, gtserror.NewErrorUnauthorized(err)
|
return false, gtserror.NewErrorUnauthorized(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -146,7 +144,7 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Obtain the activity; reject unknown activities.
|
// Obtain the activity; reject unknown activities.
|
||||||
activity, errWithCode := resolveActivity(ctx, r)
|
activity, errWithCode := ap.ResolveIncomingActivity(r)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
return false, errWithCode
|
return false, errWithCode
|
||||||
}
|
}
|
||||||
|
@ -156,6 +154,7 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
|
||||||
// involved in it tangentially.
|
// involved in it tangentially.
|
||||||
ctx, err = f.sideEffectActor.PostInboxRequestBodyHook(ctx, r, activity)
|
ctx, err = f.sideEffectActor.PostInboxRequestBodyHook(ctx, r, activity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
err := gtserror.Newf("error during post inbox request body hook: %w", err)
|
||||||
return false, gtserror.NewErrorInternalError(err)
|
return false, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +173,7 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Real error has occurred.
|
// Real error has occurred.
|
||||||
|
err := gtserror.Newf("error authorizing post inbox: %w", err)
|
||||||
return false, gtserror.NewErrorInternalError(err)
|
return false, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,8 +181,8 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
|
||||||
// Block exists either from this instance against
|
// Block exists either from this instance against
|
||||||
// one or more directly involved actors, or between
|
// one or more directly involved actors, or between
|
||||||
// receiving account and one of those actors.
|
// receiving account and one of those actors.
|
||||||
err = errors.New("blocked")
|
const text = "blocked"
|
||||||
return false, gtserror.NewErrorForbidden(err)
|
return false, gtserror.NewErrorForbidden(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy existing URL + add request host and scheme.
|
// Copy existing URL + add request host and scheme.
|
||||||
|
@ -205,13 +205,13 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
|
||||||
// Send the rejection to the peer.
|
// Send the rejection to the peer.
|
||||||
if errors.Is(err, pub.ErrObjectRequired) || errors.Is(err, pub.ErrTargetRequired) {
|
if errors.Is(err, pub.ErrObjectRequired) || errors.Is(err, pub.ErrTargetRequired) {
|
||||||
// Log the original error but return something a bit more generic.
|
// Log the original error but return something a bit more generic.
|
||||||
l.Debugf("malformed incoming Activity: %q", err)
|
log.Warnf(ctx, "malformed incoming activity: %v", err)
|
||||||
err = errors.New("malformed incoming Activity: an Object and/or Target was required but not set")
|
const text = "malformed activity: missing Object and / or Target"
|
||||||
return false, gtserror.NewErrorBadRequest(err, err.Error())
|
return false, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// There's been some real error.
|
// There's been some real error.
|
||||||
err = fmt.Errorf("PostInboxScheme: error calling sideEffectActor.PostInbox: %w", err)
|
err := gtserror.Newf("error calling sideEffectActor.PostInbox: %w", err)
|
||||||
return false, gtserror.NewErrorInternalError(err)
|
return false, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,7 +241,7 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
|
||||||
) {
|
) {
|
||||||
// Failed inbox forwarding is not a show-stopper,
|
// Failed inbox forwarding is not a show-stopper,
|
||||||
// and doesn't even necessarily denote a real error.
|
// and doesn't even necessarily denote a real error.
|
||||||
l.Warnf("error calling sideEffectActor.InboxForwarding: %q", err)
|
l.Warnf("error calling sideEffectActor.InboxForwarding: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,58 +250,6 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveActivity is a util function for pulling a
|
|
||||||
// pub.Activity type out of an incoming POST request.
|
|
||||||
func resolveActivity(ctx context.Context, r *http.Request) (pub.Activity, gtserror.WithCode) {
|
|
||||||
// Tidy up when done.
|
|
||||||
defer r.Body.Close()
|
|
||||||
|
|
||||||
b, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("error reading request body: %w", err)
|
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawActivity map[string]interface{}
|
|
||||||
if err := json.Unmarshal(b, &rawActivity); err != nil {
|
|
||||||
err = fmt.Errorf("error unmarshalling request body: %w", err)
|
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := streams.ToType(ctx, rawActivity)
|
|
||||||
if err != nil {
|
|
||||||
if !streams.IsUnmatchedErr(err) {
|
|
||||||
// Real error.
|
|
||||||
err = fmt.Errorf("error matching json to type: %w", err)
|
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Respond with bad request; we just couldn't
|
|
||||||
// match the type to one that we know about.
|
|
||||||
err = errors.New("body json could not be resolved to ActivityStreams value")
|
|
||||||
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
activity, ok := t.(pub.Activity)
|
|
||||||
if !ok {
|
|
||||||
err = fmt.Errorf("ActivityStreams value with type %T is not a pub.Activity", t)
|
|
||||||
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if activity.GetJSONLDId() == nil {
|
|
||||||
err = fmt.Errorf("incoming Activity %s did not have required id property set", activity.GetTypeName())
|
|
||||||
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// If activity Object is a Statusable, we'll want to replace the
|
|
||||||
// parsed `content` value with the value from the raw JSON instead.
|
|
||||||
// See https://github.com/superseriousbusiness/gotosocial/issues/1661
|
|
||||||
// Likewise, if it's an Accountable, we'll normalize some fields on it.
|
|
||||||
ap.NormalizeIncomingActivityObject(activity, rawActivity)
|
|
||||||
|
|
||||||
return activity, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Functions below are just lightly wrapped versions
|
Functions below are just lightly wrapped versions
|
||||||
of the original go-fed federatingActor functions.
|
of the original go-fed federatingActor functions.
|
||||||
|
|
Loading…
Reference in a new issue