[feature] Forward-compatibility with Approval objects (#3807)

* vendor

* [feature] Forward-compatibility with Approval objects

* vendor the thing

* fix leetle bug

* lil syntax tweak for beloved kimb
This commit is contained in:
tobi 2025-02-19 18:09:54 +01:00 committed by GitHub
commit 96716e4f43
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
125 changed files with 20960 additions and 2964 deletions

View file

@ -97,6 +97,11 @@ const (
// Not in the AS spec, just used internally to indicate
// that we don't *yet* know what type of Object something is.
ObjectUnknown = "Unknown"
// Extensions and unofficial additions.
ObjectLikeApproval = "LikeApproval"
ObjectReplyApproval = "ReplyApproval"
ObjectAnnounceApproval = "AnnounceApproval"
)
// isActivity returns whether AS type name is of an Activity (NOT IntransitiveActivity).

View file

@ -128,16 +128,13 @@ func ToPollOptionable(t vocab.Type) (PollOptionable, bool) {
}
// IsAccept returns whether AS vocab type name
// is something that can be cast to Accept.
// is something that can be cast to Acceptable.
func IsAcceptable(typeName string) bool {
return typeName == ActivityAccept
}
// ToAcceptable safely tries to cast vocab.Type as vocab.ActivityStreamsAccept.
//
// TODO: Add additional "Accept" types here, eg., "ApproveReply" from
// https://codeberg.org/fediverse/fep/src/branch/main/fep/5624/fep-5624.md
func ToAcceptable(t vocab.Type) (vocab.ActivityStreamsAccept, bool) {
// ToAcceptable safely tries to cast vocab.Type as Acceptable.
func ToAcceptable(t vocab.Type) (Acceptable, bool) {
acceptable, ok := t.(vocab.ActivityStreamsAccept)
if !ok || !IsAcceptable(t.GetTypeName()) {
return nil, false
@ -145,6 +142,28 @@ func ToAcceptable(t vocab.Type) (vocab.ActivityStreamsAccept, bool) {
return acceptable, true
}
// IsApprovable returns whether AS vocab type name
// is something that can be cast to Approvable.
func IsApprovable(typeName string) bool {
switch typeName {
case ObjectLikeApproval,
ObjectReplyApproval,
ObjectAnnounceApproval:
return true
default:
return false
}
}
// ToAcceptable safely tries to cast vocab.Type as Approvable.
func ToApprovable(t vocab.Type) (Approvable, bool) {
approvable, ok := t.(Approvable)
if !ok || !IsApprovable(t.GetTypeName()) {
return nil, false
}
return approvable, 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).
@ -247,6 +266,19 @@ type PollOptionable interface {
// interface for representing an Accept.
type Acceptable interface {
Activityable
WithTarget
WithResult
}
// Approvable represents the minimum activitypub interface
// for a LikeApproval, ReplyApproval, or AnnounceApproval.
type Approvable interface {
vocab.Type
WithAttributedTo
WithObject
WithTarget
}
// Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'. (see: IsAttachmentable).
@ -708,3 +740,9 @@ type WithApprovedBy interface {
GetGoToSocialApprovedBy() vocab.GoToSocialApprovedByProperty
SetGoToSocialApprovedBy(vocab.GoToSocialApprovedByProperty)
}
// WithVotersCount represents an activity or object the result property.
type WithResult interface {
GetActivityStreamsResult() vocab.ActivityStreamsResultProperty
SetActivityStreamsResult(vocab.ActivityStreamsResultProperty)
}

View file

@ -198,48 +198,12 @@ func ResolveCollectionPage(ctx context.Context, body io.ReadCloser) (CollectionP
return ToCollectionPageIterator(t)
}
// ResolveAcceptable tries to resolve the given reader
// into an ActivityStreams Acceptable representation.
func ResolveAcceptable(
ctx context.Context,
body io.ReadCloser,
) (Acceptable, error) {
// Get "raw" map
// destination.
raw := getMap()
// Release.
defer putMap(raw)
// Decode data as JSON into 'raw' map
// and get the resolved AS vocab.Type.
// (this handles close of given body).
t, err := decodeType(ctx, body, raw)
if err != nil {
return nil, gtserror.SetWrongType(err)
}
// Attempt to cast as acceptable.
acceptable, ok := ToAcceptable(t)
if !ok {
err := gtserror.Newf("cannot resolve vocab type %T as acceptable", t)
return nil, gtserror.SetWrongType(err)
}
return acceptable, nil
}
// emptydest is an empty JSON decode
// destination useful for "noop" decodes
// to check underlying reader is empty.
var emptydest = &struct{}{}
// decodeType tries to read and parse the data
// at provided io.ReadCloser as a JSON ActivityPub
// type, failing if not parseable as JSON or not
// resolveable as one of our known AS types.
//
// NOTE: this function handles closing
// given body when it is finished with.
// decodeType is the package-internal version of DecodeType.
//
// The given map pointer will also be populated with
// the 'raw' JSON data, for further processing.
@ -284,3 +248,23 @@ func decodeType(
return t, nil
}
// DecodeType tries to read and parse the data
// at provided io.ReadCloser as a JSON ActivityPub
// type, failing if not parseable as JSON or not
// resolveable as one of our known AS types.
//
// NOTE: this function handles closing
// given body when it is finished with.
func DecodeType(
ctx context.Context,
body io.ReadCloser,
) (vocab.Type, error) {
// Get "raw" map
// destination.
raw := getMap()
// Release.
defer putMap(raw)
return decodeType(ctx, body, raw)
}