summaryrefslogtreecommitdiff
path: root/internal/ap
diff options
context:
space:
mode:
authorLibravatar Tobi Smethurst <31960611+tsmethurst@users.noreply.github.com>2021-08-10 13:32:39 +0200
committerLibravatar GitHub <noreply@github.com>2021-08-10 13:32:39 +0200
commit0f2de6394a1c52d47e326bb7d7d129a217ae4f6f (patch)
treee2709bdbbbbcf5e12d6da62b653b67f1789ab1c5 /internal/ap
parentFrodo swaggins (#126) (diff)
downloadgotosocial-0f2de6394a1c52d47e326bb7d7d129a217ae4f6f.tar.xz
Dereference remote replies (#132)
* decided where to put reply dereferencing * fiddling with dereferencing threads * further adventures * tidy up some stuff * move dereferencing functionality * a bunch of refactoring * go fmt * more refactoring * bleep bloop * docs and linting * start implementing replies collection on gts side * fiddling around * allow dereferencing our replies * lint, fmt
Diffstat (limited to 'internal/ap')
-rw-r--r--internal/ap/extract.go614
-rw-r--r--internal/ap/interfaces.go321
2 files changed, 935 insertions, 0 deletions
diff --git a/internal/ap/extract.go b/internal/ap/extract.go
new file mode 100644
index 000000000..baffd4bf2
--- /dev/null
+++ b/internal/ap/extract.go
@@ -0,0 +1,614 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ 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 contains models and utilities for working with activitypub/activitystreams representations.
+//
+// It is built on top of go-fed/activity.
+package ap
+
+import (
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/go-fed/activity/pub"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
+)
+
+// ExtractPreferredUsername returns a string representation of an interface's preferredUsername property.
+func ExtractPreferredUsername(i WithPreferredUsername) (string, error) {
+ u := i.GetActivityStreamsPreferredUsername()
+ if u == nil || !u.IsXMLSchemaString() {
+ return "", errors.New("preferredUsername was not a string")
+ }
+ if u.GetXMLSchemaString() == "" {
+ return "", errors.New("preferredUsername was empty")
+ }
+ return u.GetXMLSchemaString(), nil
+}
+
+// ExtractName returns a string representation of an interface's name property.
+func ExtractName(i WithName) (string, error) {
+ nameProp := i.GetActivityStreamsName()
+ if nameProp == nil {
+ return "", errors.New("activityStreamsName not found")
+ }
+
+ // take the first name string we can find
+ for iter := nameProp.Begin(); iter != nameProp.End(); iter = iter.Next() {
+ if iter.IsXMLSchemaString() && iter.GetXMLSchemaString() != "" {
+ return iter.GetXMLSchemaString(), nil
+ }
+ }
+
+ return "", errors.New("activityStreamsName not found")
+}
+
+// ExtractInReplyToURI extracts the inReplyToURI property (if present) from an interface.
+func ExtractInReplyToURI(i WithInReplyTo) *url.URL {
+ inReplyToProp := i.GetActivityStreamsInReplyTo()
+ if inReplyToProp == nil {
+ // the property just wasn't set
+ return nil
+ }
+ for iter := inReplyToProp.Begin(); iter != inReplyToProp.End(); iter = iter.Next() {
+ if iter.IsIRI() {
+ if iter.GetIRI() != nil {
+ return iter.GetIRI()
+ }
+ }
+ }
+ // couldn't find a URI
+ return nil
+}
+
+// ExtractURLItems extracts a slice of URLs from a property that has withItems.
+func ExtractURLItems(i WithItems) []*url.URL {
+ urls := []*url.URL{}
+ items := i.GetActivityStreamsItems()
+ if items == nil || items.Len() == 0 {
+ return urls
+ }
+
+ for iter := items.Begin(); iter != items.End(); iter = iter.Next() {
+ if iter.IsIRI() {
+ urls = append(urls, iter.GetIRI())
+ }
+ }
+ return urls
+}
+
+// ExtractTos returns a list of URIs that the activity addresses as To.
+func ExtractTos(i WithTo) ([]*url.URL, error) {
+ to := []*url.URL{}
+ toProp := i.GetActivityStreamsTo()
+ if toProp == nil {
+ return nil, errors.New("toProp was nil")
+ }
+ for iter := toProp.Begin(); iter != toProp.End(); iter = iter.Next() {
+ if iter.IsIRI() {
+ if iter.GetIRI() != nil {
+ to = append(to, iter.GetIRI())
+ }
+ }
+ }
+ return to, nil
+}
+
+// ExtractCCs returns a list of URIs that the activity addresses as CC.
+func ExtractCCs(i WithCC) ([]*url.URL, error) {
+ cc := []*url.URL{}
+ ccProp := i.GetActivityStreamsCc()
+ if ccProp == nil {
+ return cc, nil
+ }
+ for iter := ccProp.Begin(); iter != ccProp.End(); iter = iter.Next() {
+ if iter.IsIRI() {
+ if iter.GetIRI() != nil {
+ cc = append(cc, iter.GetIRI())
+ }
+ }
+ }
+ return cc, nil
+}
+
+// ExtractAttributedTo returns the URL of the actor that the withAttributedTo is attributed to.
+func ExtractAttributedTo(i WithAttributedTo) (*url.URL, error) {
+ attributedToProp := i.GetActivityStreamsAttributedTo()
+ if attributedToProp == nil {
+ return nil, errors.New("attributedToProp was nil")
+ }
+ for iter := attributedToProp.Begin(); iter != attributedToProp.End(); iter = iter.Next() {
+ if iter.IsIRI() {
+ if iter.GetIRI() != nil {
+ return iter.GetIRI(), nil
+ }
+ }
+ }
+ return nil, errors.New("couldn't find iri for attributed to")
+}
+
+// ExtractPublished extracts the publication time of an activity.
+func ExtractPublished(i WithPublished) (time.Time, error) {
+ publishedProp := i.GetActivityStreamsPublished()
+ if publishedProp == nil {
+ return time.Time{}, errors.New("published prop was nil")
+ }
+
+ if !publishedProp.IsXMLSchemaDateTime() {
+ return time.Time{}, errors.New("published prop was not date time")
+ }
+
+ t := publishedProp.Get()
+ if t.IsZero() {
+ return time.Time{}, errors.New("published time was zero")
+ }
+ return t, nil
+}
+
+// ExtractIconURL extracts a URL to a supported image file from something like:
+// "icon": {
+// "mediaType": "image/jpeg",
+// "type": "Image",
+// "url": "http://example.org/path/to/some/file.jpeg"
+// },
+func ExtractIconURL(i WithIcon) (*url.URL, error) {
+ iconProp := i.GetActivityStreamsIcon()
+ if iconProp == nil {
+ return nil, errors.New("icon property was nil")
+ }
+
+ // icon can potentially contain multiple entries, so we iterate through all of them
+ // here in order to find the first one that meets these criteria:
+ // 1. is an image
+ // 2. has a URL so we can grab it
+ for iter := iconProp.Begin(); iter != iconProp.End(); iter = iter.Next() {
+ // 1. is an image
+ if !iter.IsActivityStreamsImage() {
+ continue
+ }
+ imageValue := iter.GetActivityStreamsImage()
+ if imageValue == nil {
+ continue
+ }
+
+ // 2. has a URL so we can grab it
+ url, err := ExtractURL(imageValue)
+ if err == nil && url != nil {
+ return url, nil
+ }
+ }
+ // if we get to this point we didn't find an icon meeting our criteria :'(
+ return nil, errors.New("could not extract valid image from icon")
+}
+
+// ExtractImageURL extracts a URL to a supported image file from something like:
+// "image": {
+// "mediaType": "image/jpeg",
+// "type": "Image",
+// "url": "http://example.org/path/to/some/file.jpeg"
+// },
+func ExtractImageURL(i WithImage) (*url.URL, error) {
+ imageProp := i.GetActivityStreamsImage()
+ if imageProp == nil {
+ return nil, errors.New("icon property was nil")
+ }
+
+ // icon can potentially contain multiple entries, so we iterate through all of them
+ // here in order to find the first one that meets these criteria:
+ // 1. is an image
+ // 2. has a URL so we can grab it
+ for iter := imageProp.Begin(); iter != imageProp.End(); iter = iter.Next() {
+ // 1. is an image
+ if !iter.IsActivityStreamsImage() {
+ continue
+ }
+ imageValue := iter.GetActivityStreamsImage()
+ if imageValue == nil {
+ continue
+ }
+
+ // 2. has a URL so we can grab it
+ url, err := ExtractURL(imageValue)
+ if err == nil && url != nil {
+ return url, nil
+ }
+ }
+ // if we get to this point we didn't find an image meeting our criteria :'(
+ return nil, errors.New("could not extract valid image from image property")
+}
+
+// ExtractSummary extracts the summary/content warning of an interface.
+func ExtractSummary(i WithSummary) (string, error) {
+ summaryProp := i.GetActivityStreamsSummary()
+ if summaryProp == nil {
+ return "", errors.New("summary property was nil")
+ }
+
+ for iter := summaryProp.Begin(); iter != summaryProp.End(); iter = iter.Next() {
+ if iter.IsXMLSchemaString() && iter.GetXMLSchemaString() != "" {
+ return iter.GetXMLSchemaString(), nil
+ }
+ }
+
+ return "", errors.New("could not extract summary")
+}
+
+// ExtractDiscoverable extracts the Discoverable boolean of an interface.
+func ExtractDiscoverable(i WithDiscoverable) (bool, error) {
+ if i.GetTootDiscoverable() == nil {
+ return false, errors.New("discoverable was nil")
+ }
+ return i.GetTootDiscoverable().Get(), nil
+}
+
+// ExtractURL extracts the URL property of an interface.
+func ExtractURL(i WithURL) (*url.URL, error) {
+ urlProp := i.GetActivityStreamsUrl()
+ if urlProp == nil {
+ return nil, errors.New("url property was nil")
+ }
+
+ for iter := urlProp.Begin(); iter != urlProp.End(); iter = iter.Next() {
+ if iter.IsIRI() && iter.GetIRI() != nil {
+ return iter.GetIRI(), nil
+ }
+ }
+
+ return nil, errors.New("could not extract url")
+}
+
+// ExtractPublicKeyForOwner extracts the public key from an interface, as long as it belongs to the specified owner.
+// It will return the public key itself, the id/URL of the public key, or an error if something goes wrong.
+func ExtractPublicKeyForOwner(i WithPublicKey, forOwner *url.URL) (*rsa.PublicKey, *url.URL, error) {
+ publicKeyProp := i.GetW3IDSecurityV1PublicKey()
+ if publicKeyProp == nil {
+ return nil, nil, errors.New("public key property was nil")
+ }
+
+ for iter := publicKeyProp.Begin(); iter != publicKeyProp.End(); iter = iter.Next() {
+ pkey := iter.Get()
+ if pkey == nil {
+ continue
+ }
+
+ pkeyID, err := pub.GetId(pkey)
+ if err != nil || pkeyID == nil {
+ continue
+ }
+
+ if pkey.GetW3IDSecurityV1Owner() == nil || pkey.GetW3IDSecurityV1Owner().Get() == nil || pkey.GetW3IDSecurityV1Owner().Get().String() != forOwner.String() {
+ continue
+ }
+
+ if pkey.GetW3IDSecurityV1PublicKeyPem() == nil {
+ continue
+ }
+
+ pkeyPem := pkey.GetW3IDSecurityV1PublicKeyPem().Get()
+ if pkeyPem == "" {
+ continue
+ }
+
+ block, _ := pem.Decode([]byte(pkeyPem))
+ if block == nil || block.Type != "PUBLIC KEY" {
+ return nil, nil, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type")
+ }
+
+ p, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ return nil, nil, fmt.Errorf("could not parse public key from block bytes: %s", err)
+ }
+ if p == nil {
+ return nil, nil, errors.New("returned public key was empty")
+ }
+
+ if publicKey, ok := p.(*rsa.PublicKey); ok {
+ return publicKey, pkeyID, nil
+ }
+ }
+ return nil, nil, errors.New("couldn't find public key")
+}
+
+// ExtractContent returns a string representation of the interface's Content property.
+func ExtractContent(i WithContent) (string, error) {
+ contentProperty := i.GetActivityStreamsContent()
+ if contentProperty == nil {
+ return "", errors.New("content property was nil")
+ }
+ for iter := contentProperty.Begin(); iter != contentProperty.End(); iter = iter.Next() {
+ if iter.IsXMLSchemaString() && iter.GetXMLSchemaString() != "" {
+ return iter.GetXMLSchemaString(), nil
+ }
+ }
+ return "", errors.New("no content found")
+}
+
+// ExtractAttachments returns a slice of attachments on the interface.
+func ExtractAttachments(i WithAttachment) ([]*gtsmodel.MediaAttachment, error) {
+ attachments := []*gtsmodel.MediaAttachment{}
+ attachmentProp := i.GetActivityStreamsAttachment()
+ if attachmentProp == nil {
+ return attachments, nil
+ }
+ for iter := attachmentProp.Begin(); iter != attachmentProp.End(); iter = iter.Next() {
+ t := iter.GetType()
+ if t == nil {
+ continue
+ }
+ attachmentable, ok := t.(Attachmentable)
+ if !ok {
+ continue
+ }
+ attachment, err := ExtractAttachment(attachmentable)
+ if err != nil {
+ continue
+ }
+ attachments = append(attachments, attachment)
+ }
+ return attachments, nil
+}
+
+// ExtractAttachment returns a gts model of an attachment from an attachmentable interface.
+func ExtractAttachment(i Attachmentable) (*gtsmodel.MediaAttachment, error) {
+ attachment := &gtsmodel.MediaAttachment{
+ File: gtsmodel.File{},
+ }
+
+ attachmentURL, err := ExtractURL(i)
+ if err != nil {
+ return nil, err
+ }
+ attachment.RemoteURL = attachmentURL.String()
+
+ mediaType := i.GetActivityStreamsMediaType()
+ if mediaType == nil {
+ return nil, errors.New("no media type")
+ }
+ if mediaType.Get() == "" {
+ return nil, errors.New("no media type")
+ }
+ attachment.File.ContentType = mediaType.Get()
+ attachment.Type = gtsmodel.FileTypeImage
+
+ name, err := ExtractName(i)
+ if err == nil {
+ attachment.Description = name
+ }
+
+ attachment.Processing = gtsmodel.ProcessingStatusReceived
+
+ return attachment, nil
+}
+
+// func extractBlurhash(i withBlurhash) (string, error) {
+// if i.GetTootBlurhashProperty() == nil {
+// return "", errors.New("blurhash property was nil")
+// }
+// if i.GetTootBlurhashProperty().Get() == "" {
+// return "", errors.New("empty blurhash string")
+// }
+// return i.GetTootBlurhashProperty().Get(), nil
+// }
+
+// ExtractHashtags returns a slice of tags on the interface.
+func ExtractHashtags(i WithTag) ([]*gtsmodel.Tag, error) {
+ tags := []*gtsmodel.Tag{}
+ tagsProp := i.GetActivityStreamsTag()
+ if tagsProp == nil {
+ return tags, nil
+ }
+ for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() {
+ t := iter.GetType()
+ if t == nil {
+ continue
+ }
+
+ if t.GetTypeName() != "Hashtag" {
+ continue
+ }
+
+ hashtaggable, ok := t.(Hashtaggable)
+ if !ok {
+ continue
+ }
+
+ tag, err := ExtractHashtag(hashtaggable)
+ if err != nil {
+ continue
+ }
+
+ tags = append(tags, tag)
+ }
+ return tags, nil
+}
+
+// ExtractHashtag returns a gtsmodel tag from a hashtaggable.
+func ExtractHashtag(i Hashtaggable) (*gtsmodel.Tag, error) {
+ tag := &gtsmodel.Tag{}
+
+ hrefProp := i.GetActivityStreamsHref()
+ if hrefProp == nil || !hrefProp.IsIRI() {
+ return nil, errors.New("no href prop")
+ }
+ tag.URL = hrefProp.GetIRI().String()
+
+ name, err := ExtractName(i)
+ if err != nil {
+ return nil, err
+ }
+ tag.Name = strings.TrimPrefix(name, "#")
+
+ return tag, nil
+}
+
+// ExtractEmojis returns a slice of emojis on the interface.
+func ExtractEmojis(i WithTag) ([]*gtsmodel.Emoji, error) {
+ emojis := []*gtsmodel.Emoji{}
+ tagsProp := i.GetActivityStreamsTag()
+ if tagsProp == nil {
+ return emojis, nil
+ }
+ for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() {
+ t := iter.GetType()
+ if t == nil {
+ continue
+ }
+
+ if t.GetTypeName() != "Emoji" {
+ continue
+ }
+
+ emojiable, ok := t.(Emojiable)
+ if !ok {
+ continue
+ }
+
+ emoji, err := ExtractEmoji(emojiable)
+ if err != nil {
+ continue
+ }
+
+ emojis = append(emojis, emoji)
+ }
+ return emojis, nil
+}
+
+// ExtractEmoji ...
+func ExtractEmoji(i Emojiable) (*gtsmodel.Emoji, error) {
+ emoji := &gtsmodel.Emoji{}
+
+ idProp := i.GetJSONLDId()
+ if idProp == nil || !idProp.IsIRI() {
+ return nil, errors.New("no id for emoji")
+ }
+ uri := idProp.GetIRI()
+ emoji.URI = uri.String()
+ emoji.Domain = uri.Host
+
+ name, err := ExtractName(i)
+ if err != nil {
+ return nil, err
+ }
+ emoji.Shortcode = strings.Trim(name, ":")
+
+ if i.GetActivityStreamsIcon() == nil {
+ return nil, errors.New("no icon for emoji")
+ }
+ imageURL, err := ExtractIconURL(i)
+ if err != nil {
+ return nil, errors.New("no url for emoji image")
+ }
+ emoji.ImageRemoteURL = imageURL.String()
+
+ return emoji, nil
+}
+
+// ExtractMentions extracts a slice of gtsmodel Mentions from a WithTag interface.
+func ExtractMentions(i WithTag) ([]*gtsmodel.Mention, error) {
+ mentions := []*gtsmodel.Mention{}
+ tagsProp := i.GetActivityStreamsTag()
+ if tagsProp == nil {
+ return mentions, nil
+ }
+ for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() {
+ t := iter.GetType()
+ if t == nil {
+ continue
+ }
+
+ if t.GetTypeName() != "Mention" {
+ continue
+ }
+
+ mentionable, ok := t.(Mentionable)
+ if !ok {
+ continue
+ }
+
+ mention, err := ExtractMention(mentionable)
+ if err != nil {
+ continue
+ }
+
+ mentions = append(mentions, mention)
+ }
+ return mentions, nil
+}
+
+// ExtractMention extracts a gts model mention from a Mentionable.
+func ExtractMention(i Mentionable) (*gtsmodel.Mention, error) {
+ mention := &gtsmodel.Mention{}
+
+ mentionString, err := ExtractName(i)
+ if err != nil {
+ return nil, err
+ }
+
+ // just make sure the mention string is valid so we can handle it properly later on...
+ username, domain, err := util.ExtractMentionParts(mentionString)
+ if err != nil {
+ return nil, err
+ }
+ if username == "" || domain == "" {
+ return nil, errors.New("username or domain was empty")
+ }
+ mention.NameString = mentionString
+
+ // the href prop should be the AP URI of a user we know, eg https://example.org/users/whatever_user
+ hrefProp := i.GetActivityStreamsHref()
+ if hrefProp == nil || !hrefProp.IsIRI() {
+ return nil, errors.New("no href prop")
+ }
+ mention.MentionedAccountURI = hrefProp.GetIRI().String()
+ return mention, nil
+}
+
+// ExtractActor extracts the actor ID/IRI from an interface WithActor.
+func ExtractActor(i WithActor) (*url.URL, error) {
+ actorProp := i.GetActivityStreamsActor()
+ if actorProp == nil {
+ return nil, errors.New("actor property was nil")
+ }
+ for iter := actorProp.Begin(); iter != actorProp.End(); iter = iter.Next() {
+ if iter.IsIRI() && iter.GetIRI() != nil {
+ return iter.GetIRI(), nil
+ }
+ }
+ return nil, errors.New("no iri found for actor prop")
+}
+
+// ExtractObject extracts a URL object from a WithObject interface.
+func ExtractObject(i WithObject) (*url.URL, error) {
+ objectProp := i.GetActivityStreamsObject()
+ if objectProp == nil {
+ return nil, errors.New("object property was nil")
+ }
+ for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() {
+ if iter.IsIRI() && iter.GetIRI() != nil {
+ return iter.GetIRI(), nil
+ }
+ }
+ return nil, errors.New("no iri found for object prop")
+}
diff --git a/internal/ap/interfaces.go b/internal/ap/interfaces.go
new file mode 100644
index 000000000..43dd149d5
--- /dev/null
+++ b/internal/ap/interfaces.go
@@ -0,0 +1,321 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ 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 "github.com/go-fed/activity/streams/vocab"
+
+// Accountable represents the minimum activitypub interface for representing an 'account'.
+// This interface is fulfilled by: Person, Application, Organization, Service, and Group
+type Accountable interface {
+ WithJSONLDId
+ WithTypeName
+
+ WithPreferredUsername
+ WithIcon
+ WithName
+ WithImage
+ WithSummary
+ WithDiscoverable
+ WithURL
+ WithPublicKey
+ WithInbox
+ WithOutbox
+ WithFollowing
+ WithFollowers
+ WithFeatured
+}
+
+// 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
+type Statusable interface {
+ WithJSONLDId
+ WithTypeName
+
+ WithSummary
+ WithInReplyTo
+ WithPublished
+ WithURL
+ WithAttributedTo
+ WithTo
+ WithCC
+ WithSensitive
+ WithConversation
+ WithContent
+ WithAttachment
+ WithTag
+ WithReplies
+}
+
+// Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'.
+// This interface is fulfilled by: Audio, Document, Image, Video
+type Attachmentable interface {
+ WithTypeName
+ WithMediaType
+ WithURL
+ WithName
+}
+
+// Hashtaggable represents the minimum activitypub interface for representing a 'hashtag' tag.
+type Hashtaggable interface {
+ WithTypeName
+ WithHref
+ WithName
+}
+
+// Emojiable represents the minimum interface for an 'emoji' tag.
+type Emojiable interface {
+ WithJSONLDId
+ WithTypeName
+ WithName
+ WithUpdated
+ WithIcon
+}
+
+// Mentionable represents the minimum interface for a 'mention' tag.
+type Mentionable interface {
+ WithName
+ WithHref
+}
+
+// Followable represents the minimum interface for an activitystreams 'follow' activity.
+type Followable interface {
+ WithJSONLDId
+ WithTypeName
+
+ WithActor
+ WithObject
+}
+
+// Likeable represents the minimum interface for an activitystreams 'like' activity.
+type Likeable interface {
+ WithJSONLDId
+ WithTypeName
+
+ WithActor
+ WithObject
+}
+
+// Blockable represents the minimum interface for an activitystreams 'block' activity.
+type Blockable interface {
+ WithJSONLDId
+ WithTypeName
+
+ WithActor
+ WithObject
+}
+
+// Announceable represents the minimum interface for an activitystreams 'announce' activity.
+type Announceable interface {
+ WithJSONLDId
+ WithTypeName
+
+ WithActor
+ WithObject
+ WithPublished
+ WithTo
+ WithCC
+}
+
+// CollectionPageable represents the minimum interface for an activitystreams 'CollectionPage' object.
+type CollectionPageable interface {
+ WithJSONLDId
+ WithTypeName
+
+ WithNext
+ WithPartOf
+ WithItems
+}
+
+// WithJSONLDId represents an activity with JSONLDIdProperty
+type WithJSONLDId interface {
+ GetJSONLDId() vocab.JSONLDIdProperty
+}
+
+// WithTypeName represents an activity with a type name
+type WithTypeName interface {
+ GetTypeName() string
+}
+
+// WithPreferredUsername represents an activity with ActivityStreamsPreferredUsernameProperty
+type WithPreferredUsername interface {
+ GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty
+}
+
+// WithIcon represents an activity with ActivityStreamsIconProperty
+type WithIcon interface {
+ GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty
+}
+
+// WithName represents an activity with ActivityStreamsNameProperty
+type WithName interface {
+ GetActivityStreamsName() vocab.ActivityStreamsNameProperty
+}
+
+// WithImage represents an activity with ActivityStreamsImageProperty
+type WithImage interface {
+ GetActivityStreamsImage() vocab.ActivityStreamsImageProperty
+}
+
+// WithSummary represents an activity with ActivityStreamsSummaryProperty
+type WithSummary interface {
+ GetActivityStreamsSummary() vocab.ActivityStreamsSummaryProperty
+}
+
+// WithDiscoverable represents an activity with TootDiscoverableProperty
+type WithDiscoverable interface {
+ GetTootDiscoverable() vocab.TootDiscoverableProperty
+}
+
+// WithURL represents an activity with ActivityStreamsUrlProperty
+type WithURL interface {
+ GetActivityStreamsUrl() vocab.ActivityStreamsUrlProperty
+}
+
+// WithPublicKey represents an activity with W3IDSecurityV1PublicKeyProperty
+type WithPublicKey interface {
+ GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty
+}
+
+// WithInbox represents an activity with ActivityStreamsInboxProperty
+type WithInbox interface {
+ GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty
+}
+
+// WithOutbox represents an activity with ActivityStreamsOutboxProperty
+type WithOutbox interface {
+ GetActivityStreamsOutbox() vocab.ActivityStreamsOutboxProperty
+}
+
+// WithFollowing represents an activity with ActivityStreamsFollowingProperty
+type WithFollowing interface {
+ GetActivityStreamsFollowing() vocab.ActivityStreamsFollowingProperty
+}
+
+// WithFollowers represents an activity with ActivityStreamsFollowersProperty
+type WithFollowers interface {
+ GetActivityStreamsFollowers() vocab.ActivityStreamsFollowersProperty
+}
+
+// WithFeatured represents an activity with TootFeaturedProperty
+type WithFeatured interface {
+ GetTootFeatured() vocab.TootFeaturedProperty
+}
+
+// WithAttributedTo represents an activity with ActivityStreamsAttributedToProperty
+type WithAttributedTo interface {
+ GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty
+}
+
+// WithAttachment represents an activity with ActivityStreamsAttachmentProperty
+type WithAttachment interface {
+ GetActivityStreamsAttachment() vocab.ActivityStreamsAttachmentProperty
+}
+
+// WithTo represents an activity with ActivityStreamsToProperty
+type WithTo interface {
+ GetActivityStreamsTo() vocab.ActivityStreamsToProperty
+}
+
+// WithInReplyTo represents an activity with ActivityStreamsInReplyToProperty
+type WithInReplyTo interface {
+ GetActivityStreamsInReplyTo() vocab.ActivityStreamsInReplyToProperty
+}
+
+// WithCC represents an activity with ActivityStreamsCcProperty
+type WithCC interface {
+ GetActivityStreamsCc() vocab.ActivityStreamsCcProperty
+}
+
+// WithSensitive ...
+type WithSensitive interface {
+ // TODO
+}
+
+// WithConversation ...
+type WithConversation interface {
+ // TODO
+}
+
+// WithContent represents an activity with ActivityStreamsContentProperty
+type WithContent interface {
+ GetActivityStreamsContent() vocab.ActivityStreamsContentProperty
+}
+
+// WithPublished represents an activity with ActivityStreamsPublishedProperty
+type WithPublished interface {
+ GetActivityStreamsPublished() vocab.ActivityStreamsPublishedProperty
+}
+
+// WithTag represents an activity with ActivityStreamsTagProperty
+type WithTag interface {
+ GetActivityStreamsTag() vocab.ActivityStreamsTagProperty
+}
+
+// WithReplies represents an activity with ActivityStreamsRepliesProperty
+type WithReplies interface {
+ GetActivityStreamsReplies() vocab.ActivityStreamsRepliesProperty
+}
+
+// WithMediaType represents an activity with ActivityStreamsMediaTypeProperty
+type WithMediaType interface {
+ GetActivityStreamsMediaType() vocab.ActivityStreamsMediaTypeProperty
+}
+
+// type withBlurhash interface {
+// GetTootBlurhashProperty() vocab.TootBlurhashProperty
+// }
+
+// type withFocalPoint interface {
+// // TODO
+// }
+
+// WithHref represents an activity with ActivityStreamsHrefProperty
+type WithHref interface {
+ GetActivityStreamsHref() vocab.ActivityStreamsHrefProperty
+}
+
+// WithUpdated represents an activity with ActivityStreamsUpdatedProperty
+type WithUpdated interface {
+ GetActivityStreamsUpdated() vocab.ActivityStreamsUpdatedProperty
+}
+
+// WithActor represents an activity with ActivityStreamsActorProperty
+type WithActor interface {
+ GetActivityStreamsActor() vocab.ActivityStreamsActorProperty
+}
+
+// WithObject represents an activity with ActivityStreamsObjectProperty
+type WithObject interface {
+ GetActivityStreamsObject() vocab.ActivityStreamsObjectProperty
+}
+
+// WithNext represents an activity with ActivityStreamsNextProperty
+type WithNext interface {
+ GetActivityStreamsNext() vocab.ActivityStreamsNextProperty
+}
+
+// WithPartOf represents an activity with ActivityStreamsPartOfProperty
+type WithPartOf interface {
+ GetActivityStreamsPartOf() vocab.ActivityStreamsPartOfProperty
+}
+
+// WithItems represents an activity with ActivityStreamsItemsProperty
+type WithItems interface {
+ GetActivityStreamsItems() vocab.ActivityStreamsItemsProperty
+}