summaryrefslogtreecommitdiff
path: root/internal/ap
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2023-11-04 20:21:20 +0000
committerLibravatar GitHub <noreply@github.com>2023-11-04 20:21:20 +0000
commit41435a6c4ee0a5b52528890edf3fbf5a9dc0a6c8 (patch)
tree987b5d7787b24f6f6e340bbcf7fd1b052fe40dfc /internal/ap
parent[docs/bugfix] fix link to swagger yaml (#2333) (diff)
downloadgotosocial-41435a6c4ee0a5b52528890edf3fbf5a9dc0a6c8.tar.xz
[feature] support canceling scheduled tasks, some federation API performance improvements (#2329)
Diffstat (limited to 'internal/ap')
-rw-r--r--internal/ap/extract.go101
-rw-r--r--internal/ap/interfaces.go98
-rw-r--r--internal/ap/properties.go325
3 files changed, 510 insertions, 14 deletions
diff --git a/internal/ap/extract.go b/internal/ap/extract.go
index 74c4497b3..332550578 100644
--- a/internal/ap/extract.go
+++ b/internal/ap/extract.go
@@ -91,6 +91,105 @@ func ExtractActivityData(activity pub.Activity, rawJSON map[string]any) ([]TypeO
}
}
+// ExtractAccountables extracts Accountable objects from a slice TypeOrIRI, returning extracted and remaining TypeOrIRIs.
+func ExtractAccountables(arr []TypeOrIRI) ([]Accountable, []TypeOrIRI) {
+ var accounts []Accountable
+
+ for i := 0; i < len(arr); i++ {
+ elem := arr[i]
+
+ if elem.IsIRI() {
+ // skip IRIs
+ continue
+ }
+
+ // Extract AS vocab type
+ // associated with elem.
+ t := elem.GetType()
+
+ // Try cast AS type as Accountable.
+ account, ok := ToAccountable(t)
+ if !ok {
+ continue
+ }
+
+ // Add casted accountable type.
+ accounts = append(accounts, account)
+
+ // Drop elem from slice.
+ copy(arr[:i], arr[i+1:])
+ arr = arr[:len(arr)-1]
+ }
+
+ return accounts, arr
+}
+
+// ExtractStatusables extracts Statusable objects from a slice TypeOrIRI, returning extracted and remaining TypeOrIRIs.
+func ExtractStatusables(arr []TypeOrIRI) ([]Statusable, []TypeOrIRI) {
+ var statuses []Statusable
+
+ for i := 0; i < len(arr); i++ {
+ elem := arr[i]
+
+ if elem.IsIRI() {
+ // skip IRIs
+ continue
+ }
+
+ // Extract AS vocab type
+ // associated with elem.
+ t := elem.GetType()
+
+ // Try cast AS type as Statusable.
+ status, ok := ToStatusable(t)
+ if !ok {
+ continue
+ }
+
+ // Add casted Statusable type.
+ statuses = append(statuses, status)
+
+ // Drop elem from slice.
+ copy(arr[:i], arr[i+1:])
+ arr = arr[:len(arr)-1]
+ }
+
+ return statuses, arr
+}
+
+// ExtractPollOptionables extracts PollOptionable objects from a slice TypeOrIRI, returning extracted and remaining TypeOrIRIs.
+func ExtractPollOptionables(arr []TypeOrIRI) ([]PollOptionable, []TypeOrIRI) {
+ var options []PollOptionable
+
+ for i := 0; i < len(arr); i++ {
+ elem := arr[i]
+
+ if elem.IsIRI() {
+ // skip IRIs
+ continue
+ }
+
+ // Extract AS vocab type
+ // associated with elem.
+ t := elem.GetType()
+
+ // Try cast as PollOptionable.
+ option, ok := ToPollOptionable(t)
+ if !ok {
+ continue
+ }
+
+ // Add casted PollOptionable type.
+ options = append(options, option)
+
+ // Drop elem from slice.
+ copy(arr[:i], arr[i+1:])
+ arr = arr[:len(arr)-1]
+ }
+
+ return options, arr
+}
+
// ExtractPreferredUsername returns a string representation of
// an interface's preferredUsername property. Will return an
// error if preferredUsername is nil, not a string, or empty.
@@ -192,7 +291,7 @@ func ExtractToURIs(i WithTo) []*url.URL {
// ExtractCcURIs returns a slice of URIs
// that the given WithCC addresses as Cc.
-func ExtractCcURIs(i WithCC) []*url.URL {
+func ExtractCcURIs(i WithCc) []*url.URL {
ccProp := i.GetActivityStreamsCc()
if ccProp == nil {
return nil
diff --git a/internal/ap/interfaces.go b/internal/ap/interfaces.go
index 6ba3c3735..fed69d69d 100644
--- a/internal/ap/interfaces.go
+++ b/internal/ap/interfaces.go
@@ -23,6 +23,21 @@ import (
"github.com/superseriousbusiness/activity/streams/vocab"
)
+// IsActivityable returns whether AS vocab type name is acceptable as Activityable.
+func IsActivityable(typeName string) bool {
+ return isActivity(typeName) ||
+ isIntransitiveActivity(typeName)
+}
+
+// ToActivityable safely tries to cast vocab.Type as Activityable, also checking for expected AS type names.
+func ToActivityable(t vocab.Type) (Activityable, bool) {
+ activityable, ok := t.(Activityable)
+ if !ok || !IsActivityable(t.GetTypeName()) {
+ return nil, false
+ }
+ return activityable, true
+}
+
// IsAccountable returns whether AS vocab type name is acceptable as Accountable.
func IsAccountable(typeName string) bool {
switch typeName {
@@ -88,6 +103,43 @@ func ToPollable(t vocab.Type) (Pollable, bool) {
return pollable, true
}
+// IsPollOptionable returns whether AS vocab type name is acceptable as PollOptionable.
+func IsPollOptionable(typeName string) bool {
+ return typeName == ObjectNote
+}
+
+// ToPollOptionable safely tries to cast vocab.Type as PollOptionable, also checking for expected AS type names.
+func ToPollOptionable(t vocab.Type) (PollOptionable, bool) {
+ note, ok := t.(vocab.ActivityStreamsNote)
+ if !ok || !IsPollOptionable(t.GetTypeName()) {
+ return nil, false
+ }
+ if note.GetActivityStreamsContent() != nil ||
+ note.GetActivityStreamsName() == nil {
+ // A PollOption is an ActivityStreamsNote
+ // WITHOUT a content property, instead only
+ // a name property.
+ return nil, false
+ }
+ return note, true
+}
+
+// Activityable represents the minimum activitypub interface for representing an 'activity'.
+// (see: IsActivityable() for types implementing this, though you MUST make sure to check
+// the typeName as this bare interface may be implementable by non-Activityable types).
+type Activityable interface {
+ // Activity is also a vocab.Type
+ vocab.Type
+
+ WithTo
+ WithCc
+ WithBcc
+ WithAttributedTo
+ WithActor
+ WithObject
+ WithPublished
+}
+
// Accountable represents the minimum activitypub interface for representing an 'account'.
// (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).
@@ -126,7 +178,7 @@ type Statusable interface {
WithURL
WithAttributedTo
WithTo
- WithCC
+ WithCc
WithSensitive
WithConversation
WithContent
@@ -145,16 +197,21 @@ type Pollable interface {
WithClosed
WithVotersCount
- // base-interface
+ // base-interfaces
Statusable
}
-// PollOptionable represents the minimum activitypub interface for representing a poll 'option'.
-// (see: IsPollOptionable() for types implementing this).
+// PollOptionable represents the minimum activitypub interface for representing a poll 'vote'.
+// (see: IsPollOptionable() 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 PollOptionable interface {
- WithTypeName
+ vocab.Type
+
WithName
+ WithTo
+ WithInReplyTo
WithReplies
+ WithAttributedTo
}
// Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'. (see: IsAttachmentable).
@@ -226,13 +283,13 @@ type Announceable interface {
WithObject
WithPublished
WithTo
- WithCC
+ WithCc
}
// Addressable represents the minimum interface for an addressed activity.
type Addressable interface {
WithTo
- WithCC
+ WithCc
}
// ReplyToable represents the minimum interface for an Activity that can be InReplyTo another activity.
@@ -268,6 +325,15 @@ type TypeOrIRI interface {
WithType
}
+// Property represents the minimum interface for an ActivityStreams property with IRIs.
+type Property[T TypeOrIRI] interface {
+ Len() int
+ At(int) T
+
+ AppendIRI(*url.URL)
+ SetIRI(int, *url.URL)
+}
+
// WithJSONLDId represents an activity with JSONLDIdProperty.
type WithJSONLDId interface {
GetJSONLDId() vocab.JSONLDIdProperty
@@ -386,18 +452,24 @@ type WithTo interface {
SetActivityStreamsTo(vocab.ActivityStreamsToProperty)
}
+// WithCC represents an activity with ActivityStreamsCcProperty
+type WithCc interface {
+ GetActivityStreamsCc() vocab.ActivityStreamsCcProperty
+ SetActivityStreamsCc(vocab.ActivityStreamsCcProperty)
+}
+
+// WithCC represents an activity with ActivityStreamsBccProperty
+type WithBcc interface {
+ GetActivityStreamsBcc() vocab.ActivityStreamsBccProperty
+ SetActivityStreamsBcc(vocab.ActivityStreamsBccProperty)
+}
+
// WithInReplyTo represents an activity with ActivityStreamsInReplyToProperty
type WithInReplyTo interface {
GetActivityStreamsInReplyTo() vocab.ActivityStreamsInReplyToProperty
SetActivityStreamsInReplyTo(vocab.ActivityStreamsInReplyToProperty)
}
-// WithCC represents an activity with ActivityStreamsCcProperty
-type WithCC interface {
- GetActivityStreamsCc() vocab.ActivityStreamsCcProperty
- SetActivityStreamsCc(vocab.ActivityStreamsCcProperty)
-}
-
// WithSensitive represents an activity with ActivityStreamsSensitiveProperty
type WithSensitive interface {
GetActivityStreamsSensitive() vocab.ActivityStreamsSensitiveProperty
diff --git a/internal/ap/properties.go b/internal/ap/properties.go
new file mode 100644
index 000000000..d8441003f
--- /dev/null
+++ b/internal/ap/properties.go
@@ -0,0 +1,325 @@
+// 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 (
+ "fmt"
+ "net/url"
+ "time"
+
+ "github.com/superseriousbusiness/activity/streams"
+ "github.com/superseriousbusiness/activity/streams/vocab"
+)
+
+// MustGet performs the given 'Get$Property(with) (T, error)' signature function, panicking on error.
+// func MustGet[W, T any](fn func(W) (T, error), with W) T {
+// t, err := fn(with)
+// if err != nil {
+// panicfAt(3, "error getting property on %T: %w", with, err)
+// }
+// return t
+// }
+
+// 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)
+// }
+// }
+
+// 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) {
+// err := fn(with, values...)
+// if err != nil {
+// panicfAt(3, "error appending properties on %T: %w", with, err)
+// }
+// }
+
+// GetJSONLDId returns the ID of 'with', or nil.
+func GetJSONLDId(with WithJSONLDId) *url.URL {
+ idProp := with.GetJSONLDId()
+ if idProp == nil {
+ return nil
+ }
+ id := idProp.Get()
+ if id == nil {
+ return nil
+ }
+ return id
+}
+
+// SetJSONLDId sets the given URL to the JSONLD ID of 'with'.
+func SetJSONLDId(with WithJSONLDId, id *url.URL) {
+ idProp := with.GetJSONLDId()
+ if idProp == nil {
+ idProp = streams.NewJSONLDIdProperty()
+ }
+ idProp.SetIRI(id)
+ with.SetJSONLDId(idProp)
+}
+
+// SetJSONLDIdStr sets the given string to the JSONLDID of 'with'. Returns error
+func SetJSONLDIdStr(with WithJSONLDId, id string) error {
+ u, err := url.Parse(id)
+ if err != nil {
+ return fmt.Errorf("error parsing id url: %w", err)
+ }
+ SetJSONLDId(with, u)
+ return nil
+}
+
+// GetTo returns the IRIs contained in the To property of 'with'. Panics on entries with missing ID.
+func GetTo(with WithTo) []*url.URL {
+ toProp := with.GetActivityStreamsTo()
+ return getIRIs[vocab.ActivityStreamsToPropertyIterator](toProp)
+}
+
+// AppendTo appends the given IRIs to the To property of 'with'.
+func AppendTo(with WithTo, to ...*url.URL) {
+ appendIRIs(func() Property[vocab.ActivityStreamsToPropertyIterator] {
+ toProp := with.GetActivityStreamsTo()
+ if toProp == nil {
+ toProp = streams.NewActivityStreamsToProperty()
+ with.SetActivityStreamsTo(toProp)
+ }
+ return toProp
+ }, to...)
+}
+
+// GetCc returns the IRIs contained in the Cc property of 'with'. Panics on entries with missing ID.
+func GetCc(with WithCc) []*url.URL {
+ ccProp := with.GetActivityStreamsCc()
+ return getIRIs[vocab.ActivityStreamsCcPropertyIterator](ccProp)
+}
+
+// AppendCc appends the given IRIs to the Cc property of 'with'.
+func AppendCc(with WithCc, cc ...*url.URL) {
+ appendIRIs(func() Property[vocab.ActivityStreamsCcPropertyIterator] {
+ ccProp := with.GetActivityStreamsCc()
+ if ccProp == nil {
+ ccProp = streams.NewActivityStreamsCcProperty()
+ with.SetActivityStreamsCc(ccProp)
+ }
+ return ccProp
+ }, cc...)
+}
+
+// GetBcc returns the IRIs contained in the Bcc property of 'with'. Panics on entries with missing ID.
+func GetBcc(with WithBcc) []*url.URL {
+ bccProp := with.GetActivityStreamsBcc()
+ return getIRIs[vocab.ActivityStreamsBccPropertyIterator](bccProp)
+}
+
+// AppendBcc appends the given IRIs to the Bcc property of 'with'.
+func AppendBcc(with WithBcc, bcc ...*url.URL) {
+ appendIRIs(func() Property[vocab.ActivityStreamsBccPropertyIterator] {
+ bccProp := with.GetActivityStreamsBcc()
+ if bccProp == nil {
+ bccProp = streams.NewActivityStreamsBccProperty()
+ with.SetActivityStreamsBcc(bccProp)
+ }
+ return bccProp
+ }, bcc...)
+}
+
+// GetActor returns the IRIs contained in the Actor property of 'with'. Panics on entries with missing ID.
+func GetActor(with WithActor) []*url.URL {
+ actorProp := with.GetActivityStreamsActor()
+ return getIRIs[vocab.ActivityStreamsActorPropertyIterator](actorProp)
+}
+
+// AppendActor appends the given IRIs to the Actor property of 'with'.
+func AppendActor(with WithActor, actor ...*url.URL) {
+ appendIRIs(func() Property[vocab.ActivityStreamsActorPropertyIterator] {
+ actorProp := with.GetActivityStreamsActor()
+ if actorProp == nil {
+ actorProp = streams.NewActivityStreamsActorProperty()
+ with.SetActivityStreamsActor(actorProp)
+ }
+ return actorProp
+ }, actor...)
+}
+
+// GetAttributedTo returns the IRIs contained in the AttributedTo property of 'with'. Panics on entries with missing ID.
+func GetAttributedTo(with WithAttributedTo) []*url.URL {
+ attribProp := with.GetActivityStreamsAttributedTo()
+ return getIRIs[vocab.ActivityStreamsAttributedToPropertyIterator](attribProp)
+}
+
+// AppendAttributedTo appends the given IRIs to the AttributedTo property of 'with'.
+func AppendAttributedTo(with WithAttributedTo, attribTo ...*url.URL) {
+ appendIRIs(func() Property[vocab.ActivityStreamsAttributedToPropertyIterator] {
+ attribProp := with.GetActivityStreamsAttributedTo()
+ if attribProp == nil {
+ attribProp = streams.NewActivityStreamsAttributedToProperty()
+ with.SetActivityStreamsAttributedTo(attribProp)
+ }
+ return attribProp
+ }, attribTo...)
+}
+
+// GetInReplyTo returns the IRIs contained in the InReplyTo property of 'with'. Panics on entries with missing ID.
+func GetInReplyTo(with WithInReplyTo) []*url.URL {
+ replyProp := with.GetActivityStreamsInReplyTo()
+ return getIRIs[vocab.ActivityStreamsInReplyToPropertyIterator](replyProp)
+}
+
+// AppendInReplyTo appends the given IRIs to the InReplyTo property of 'with'.
+func AppendInReplyTo(with WithInReplyTo, replyTo ...*url.URL) {
+ appendIRIs(func() Property[vocab.ActivityStreamsInReplyToPropertyIterator] {
+ replyProp := with.GetActivityStreamsInReplyTo()
+ if replyProp == nil {
+ replyProp = streams.NewActivityStreamsInReplyToProperty()
+ with.SetActivityStreamsInReplyTo(replyProp)
+ }
+ return replyProp
+ }, replyTo...)
+}
+
+// GetPublished returns the time contained in the Published property of 'with'.
+func GetPublished(with WithPublished) time.Time {
+ publishProp := with.GetActivityStreamsPublished()
+ if publishProp == nil {
+ return time.Time{}
+ }
+ return publishProp.Get()
+}
+
+// SetPublished sets the given time on the Published property of 'with'.
+func SetPublished(with WithPublished, published time.Time) {
+ publishProp := with.GetActivityStreamsPublished()
+ if publishProp == nil {
+ publishProp = streams.NewActivityStreamsPublishedProperty()
+ with.SetActivityStreamsPublished(publishProp)
+ }
+ publishProp.Set(published)
+}
+
+// GetEndTime returns the time contained in the EndTime property of 'with'.
+func GetEndTime(with WithEndTime) time.Time {
+ endTimeProp := with.GetActivityStreamsEndTime()
+ if endTimeProp == nil {
+ return time.Time{}
+ }
+ return endTimeProp.Get()
+}
+
+// SetEndTime sets the given time on the EndTime property of 'with'.
+func SetEndTime(with WithEndTime, end time.Time) {
+ endTimeProp := with.GetActivityStreamsEndTime()
+ if endTimeProp == nil {
+ endTimeProp = streams.NewActivityStreamsEndTimeProperty()
+ with.SetActivityStreamsEndTime(endTimeProp)
+ }
+ endTimeProp.Set(end)
+}
+
+// GetEndTime returns the times contained in the Closed property of 'with'.
+func GetClosed(with WithClosed) []time.Time {
+ closedProp := with.GetActivityStreamsClosed()
+ if closedProp == nil || closedProp.Len() == 0 {
+ return nil
+ }
+ closed := make([]time.Time, 0, closedProp.Len())
+ for i := 0; i < closedProp.Len(); i++ {
+ at := closedProp.At(i)
+ if t := at.GetXMLSchemaDateTime(); !t.IsZero() {
+ closed = append(closed, t)
+ }
+ }
+ return closed
+}
+
+// AppendClosed appends the given times to the Closed property of 'with'.
+func AppendClosed(with WithClosed, closed ...time.Time) {
+ if len(closed) == 0 {
+ return
+ }
+ closedProp := with.GetActivityStreamsClosed()
+ if closedProp == nil {
+ closedProp = streams.NewActivityStreamsClosedProperty()
+ with.SetActivityStreamsClosed(closedProp)
+ }
+ for _, closed := range closed {
+ closedProp.AppendXMLSchemaDateTime(closed)
+ }
+}
+
+// GetVotersCount returns the integer contained in the VotersCount property of 'with', if found.
+func GetVotersCount(with WithVotersCount) int {
+ votersProp := with.GetTootVotersCount()
+ if votersProp == nil {
+ return 0
+ }
+ return votersProp.Get()
+}
+
+// SetVotersCount sets the given count on the VotersCount property of 'with'.
+func SetVotersCount(with WithVotersCount, count int) {
+ votersProp := with.GetTootVotersCount()
+ if votersProp == nil {
+ votersProp = streams.NewTootVotersCountProperty()
+ with.SetTootVotersCount(votersProp)
+ }
+ votersProp.Set(count)
+}
+
+func getIRIs[T TypeOrIRI](prop Property[T]) []*url.URL {
+ if prop == nil || prop.Len() == 0 {
+ return nil
+ }
+ ids := make([]*url.URL, 0, prop.Len())
+ for i := 0; i < prop.Len(); i++ {
+ at := prop.At(i)
+ if t := at.GetType(); t != nil {
+ id := GetJSONLDId(t)
+ if id != nil {
+ ids = append(ids, id)
+ continue
+ }
+ }
+ if at.IsIRI() {
+ id := at.GetIRI()
+ if id != nil {
+ ids = append(ids, id)
+ continue
+ }
+ }
+ }
+ return ids
+}
+
+func appendIRIs[T TypeOrIRI](getProp func() Property[T], iri ...*url.URL) {
+ if len(iri) == 0 {
+ return
+ }
+ prop := getProp()
+ if prop == nil {
+ // check outside loop.
+ panic("prop not set")
+ }
+ for _, iri := range iri {
+ prop.AppendIRI(iri)
+ }
+}
+
+// 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...))
+// }