mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2026-01-03 09:53:16 -06:00
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
This commit is contained in:
parent
0386a28b5a
commit
0f2de6394a
68 changed files with 2946 additions and 1393 deletions
|
|
@ -1,570 +0,0 @@
|
|||
/*
|
||||
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 typeutils
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
func extractInReplyToURI(i withInReplyTo) (*url.URL, error) {
|
||||
inReplyToProp := i.GetActivityStreamsInReplyTo()
|
||||
if inReplyToProp == nil {
|
||||
return nil, errors.New("in reply to prop was nil")
|
||||
}
|
||||
for iter := inReplyToProp.Begin(); iter != inReplyToProp.End(); iter = iter.Next() {
|
||||
if iter.IsIRI() {
|
||||
if iter.GetIRI() != nil {
|
||||
return iter.GetIRI(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("couldn't find iri for in reply 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
func extractDiscoverable(i withDiscoverable) (bool, error) {
|
||||
if i.GetTootDiscoverable() == nil {
|
||||
return false, errors.New("discoverable was nil")
|
||||
}
|
||||
return i.GetTootDiscoverable().Get(), nil
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func extractAttachment(i Attachmentable) (*gtsmodel.MediaAttachment, error) {
|
||||
attachment := >smodel.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
|
||||
// }
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func extractHashtag(i Hashtaggable) (*gtsmodel.Tag, error) {
|
||||
tag := >smodel.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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func extractEmoji(i Emojiable) (*gtsmodel.Emoji, error) {
|
||||
emoji := >smodel.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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func extractMention(i Mentionable) (*gtsmodel.Mention, error) {
|
||||
mention := >smodel.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
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
/*
|
||||
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 typeutils
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type withJSONLDId interface {
|
||||
GetJSONLDId() vocab.JSONLDIdProperty
|
||||
}
|
||||
|
||||
type withTypeName interface {
|
||||
GetTypeName() string
|
||||
}
|
||||
|
||||
type withPreferredUsername interface {
|
||||
GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty
|
||||
}
|
||||
|
||||
type withIcon interface {
|
||||
GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty
|
||||
}
|
||||
|
||||
type withName interface {
|
||||
GetActivityStreamsName() vocab.ActivityStreamsNameProperty
|
||||
}
|
||||
|
||||
type withImage interface {
|
||||
GetActivityStreamsImage() vocab.ActivityStreamsImageProperty
|
||||
}
|
||||
|
||||
type withSummary interface {
|
||||
GetActivityStreamsSummary() vocab.ActivityStreamsSummaryProperty
|
||||
}
|
||||
|
||||
type withDiscoverable interface {
|
||||
GetTootDiscoverable() vocab.TootDiscoverableProperty
|
||||
}
|
||||
|
||||
type withURL interface {
|
||||
GetActivityStreamsUrl() vocab.ActivityStreamsUrlProperty
|
||||
}
|
||||
|
||||
type withPublicKey interface {
|
||||
GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty
|
||||
}
|
||||
|
||||
type withInbox interface {
|
||||
GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty
|
||||
}
|
||||
|
||||
type withOutbox interface {
|
||||
GetActivityStreamsOutbox() vocab.ActivityStreamsOutboxProperty
|
||||
}
|
||||
|
||||
type withFollowing interface {
|
||||
GetActivityStreamsFollowing() vocab.ActivityStreamsFollowingProperty
|
||||
}
|
||||
|
||||
type withFollowers interface {
|
||||
GetActivityStreamsFollowers() vocab.ActivityStreamsFollowersProperty
|
||||
}
|
||||
|
||||
type withFeatured interface {
|
||||
GetTootFeatured() vocab.TootFeaturedProperty
|
||||
}
|
||||
|
||||
type withAttributedTo interface {
|
||||
GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty
|
||||
}
|
||||
|
||||
type withAttachment interface {
|
||||
GetActivityStreamsAttachment() vocab.ActivityStreamsAttachmentProperty
|
||||
}
|
||||
|
||||
type withTo interface {
|
||||
GetActivityStreamsTo() vocab.ActivityStreamsToProperty
|
||||
}
|
||||
|
||||
type withInReplyTo interface {
|
||||
GetActivityStreamsInReplyTo() vocab.ActivityStreamsInReplyToProperty
|
||||
}
|
||||
|
||||
type withCC interface {
|
||||
GetActivityStreamsCc() vocab.ActivityStreamsCcProperty
|
||||
}
|
||||
|
||||
type withSensitive interface {
|
||||
// TODO
|
||||
}
|
||||
|
||||
type withConversation interface {
|
||||
// TODO
|
||||
}
|
||||
|
||||
type withContent interface {
|
||||
GetActivityStreamsContent() vocab.ActivityStreamsContentProperty
|
||||
}
|
||||
|
||||
type withPublished interface {
|
||||
GetActivityStreamsPublished() vocab.ActivityStreamsPublishedProperty
|
||||
}
|
||||
|
||||
type withTag interface {
|
||||
GetActivityStreamsTag() vocab.ActivityStreamsTagProperty
|
||||
}
|
||||
|
||||
type withReplies interface {
|
||||
GetActivityStreamsReplies() vocab.ActivityStreamsRepliesProperty
|
||||
}
|
||||
|
||||
type withMediaType interface {
|
||||
GetActivityStreamsMediaType() vocab.ActivityStreamsMediaTypeProperty
|
||||
}
|
||||
|
||||
// type withBlurhash interface {
|
||||
// GetTootBlurhashProperty() vocab.TootBlurhashProperty
|
||||
// }
|
||||
|
||||
// type withFocalPoint interface {
|
||||
// // TODO
|
||||
// }
|
||||
|
||||
type withHref interface {
|
||||
GetActivityStreamsHref() vocab.ActivityStreamsHrefProperty
|
||||
}
|
||||
|
||||
type withUpdated interface {
|
||||
GetActivityStreamsUpdated() vocab.ActivityStreamsUpdatedProperty
|
||||
}
|
||||
|
||||
type withActor interface {
|
||||
GetActivityStreamsActor() vocab.ActivityStreamsActorProperty
|
||||
}
|
||||
|
||||
type withObject interface {
|
||||
GetActivityStreamsObject() vocab.ActivityStreamsObjectProperty
|
||||
}
|
||||
|
|
@ -24,11 +24,12 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
func (c *converter) ASRepresentationToAccount(accountable Accountable, update bool) (*gtsmodel.Account, error) {
|
||||
func (c *converter) ASRepresentationToAccount(accountable ap.Accountable, update bool) (*gtsmodel.Account, error) {
|
||||
// first check if we actually already know this account
|
||||
uriProp := accountable.GetJSONLDId()
|
||||
if uriProp == nil || !uriProp.IsIRI() {
|
||||
|
|
@ -55,7 +56,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo
|
|||
|
||||
// Username aka preferredUsername
|
||||
// We need this one so bail if it's not set.
|
||||
username, err := extractPreferredUsername(accountable)
|
||||
username, err := ap.ExtractPreferredUsername(accountable)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't extract username: %s", err)
|
||||
}
|
||||
|
|
@ -66,27 +67,27 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo
|
|||
|
||||
// avatar aka icon
|
||||
// if this one isn't extractable in a format we recognise we'll just skip it
|
||||
if avatarURL, err := extractIconURL(accountable); err == nil {
|
||||
if avatarURL, err := ap.ExtractIconURL(accountable); err == nil {
|
||||
acct.AvatarRemoteURL = avatarURL.String()
|
||||
}
|
||||
|
||||
// header aka image
|
||||
// if this one isn't extractable in a format we recognise we'll just skip it
|
||||
if headerURL, err := extractImageURL(accountable); err == nil {
|
||||
if headerURL, err := ap.ExtractImageURL(accountable); err == nil {
|
||||
acct.HeaderRemoteURL = headerURL.String()
|
||||
}
|
||||
|
||||
// display name aka name
|
||||
// we default to the username, but take the more nuanced name property if it exists
|
||||
acct.DisplayName = username
|
||||
if displayName, err := extractName(accountable); err == nil {
|
||||
if displayName, err := ap.ExtractName(accountable); err == nil {
|
||||
acct.DisplayName = displayName
|
||||
}
|
||||
|
||||
// TODO: fields aka attachment array
|
||||
|
||||
// note aka summary
|
||||
note, err := extractSummary(accountable)
|
||||
note, err := ap.ExtractSummary(accountable)
|
||||
if err == nil && note != "" {
|
||||
acct.Note = note
|
||||
}
|
||||
|
|
@ -110,13 +111,13 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo
|
|||
// discoverable
|
||||
// default to false -- take custom value if it's set though
|
||||
acct.Discoverable = false
|
||||
discoverable, err := extractDiscoverable(accountable)
|
||||
discoverable, err := ap.ExtractDiscoverable(accountable)
|
||||
if err == nil {
|
||||
acct.Discoverable = discoverable
|
||||
}
|
||||
|
||||
// url property
|
||||
url, err := extractURL(accountable)
|
||||
url, err := ap.ExtractURL(accountable)
|
||||
if err == nil {
|
||||
// take the URL if we can find it
|
||||
acct.URL = url.String()
|
||||
|
|
@ -155,7 +156,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo
|
|||
// TODO: alsoKnownAs
|
||||
|
||||
// publicKey
|
||||
pkey, pkeyURL, err := extractPublicKeyForOwner(accountable, uri)
|
||||
pkey, pkeyURL, err := ap.ExtractPublicKeyForOwner(accountable, uri)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get public key for person %s: %s", uri.String(), err)
|
||||
}
|
||||
|
|
@ -165,7 +166,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo
|
|||
return acct, nil
|
||||
}
|
||||
|
||||
func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error) {
|
||||
func (c *converter) ASStatusToStatus(statusable ap.Statusable) (*gtsmodel.Status, error) {
|
||||
status := >smodel.Status{}
|
||||
|
||||
// uri at which this status is reachable
|
||||
|
|
@ -176,49 +177,49 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e
|
|||
status.URI = uriProp.GetIRI().String()
|
||||
|
||||
// web url for viewing this status
|
||||
if statusURL, err := extractURL(statusable); err == nil {
|
||||
if statusURL, err := ap.ExtractURL(statusable); err == nil {
|
||||
status.URL = statusURL.String()
|
||||
}
|
||||
|
||||
// the html-formatted content of this status
|
||||
if content, err := extractContent(statusable); err == nil {
|
||||
if content, err := ap.ExtractContent(statusable); err == nil {
|
||||
status.Content = content
|
||||
}
|
||||
|
||||
// attachments to dereference and fetch later on (we don't do that here)
|
||||
if attachments, err := extractAttachments(statusable); err == nil {
|
||||
if attachments, err := ap.ExtractAttachments(statusable); err == nil {
|
||||
status.GTSMediaAttachments = attachments
|
||||
}
|
||||
|
||||
// hashtags to dereference later on
|
||||
if hashtags, err := extractHashtags(statusable); err == nil {
|
||||
if hashtags, err := ap.ExtractHashtags(statusable); err == nil {
|
||||
status.GTSTags = hashtags
|
||||
}
|
||||
|
||||
// emojis to dereference and fetch later on
|
||||
if emojis, err := extractEmojis(statusable); err == nil {
|
||||
if emojis, err := ap.ExtractEmojis(statusable); err == nil {
|
||||
status.GTSEmojis = emojis
|
||||
}
|
||||
|
||||
// mentions to dereference later on
|
||||
if mentions, err := extractMentions(statusable); err == nil {
|
||||
if mentions, err := ap.ExtractMentions(statusable); err == nil {
|
||||
status.GTSMentions = mentions
|
||||
}
|
||||
|
||||
// cw string for this status
|
||||
if cw, err := extractSummary(statusable); err == nil {
|
||||
if cw, err := ap.ExtractSummary(statusable); err == nil {
|
||||
status.ContentWarning = cw
|
||||
}
|
||||
|
||||
// when was this status created?
|
||||
published, err := extractPublished(statusable)
|
||||
published, err := ap.ExtractPublished(statusable)
|
||||
if err == nil {
|
||||
status.CreatedAt = published
|
||||
}
|
||||
|
||||
// which account posted this status?
|
||||
// if we don't know the account yet we can dereference it later
|
||||
attributedTo, err := extractAttributedTo(statusable)
|
||||
attributedTo, err := ap.ExtractAttributedTo(statusable)
|
||||
if err != nil {
|
||||
return nil, errors.New("attributedTo was empty")
|
||||
}
|
||||
|
|
@ -233,8 +234,8 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e
|
|||
status.GTSAuthorAccount = statusOwner
|
||||
|
||||
// check if there's a post that this is a reply to
|
||||
inReplyToURI, err := extractInReplyToURI(statusable)
|
||||
if err == nil {
|
||||
inReplyToURI := ap.ExtractInReplyToURI(statusable)
|
||||
if inReplyToURI != nil {
|
||||
// something is set so we can at least set this field on the
|
||||
// status and dereference using this later if we need to
|
||||
status.InReplyToURI = inReplyToURI.String()
|
||||
|
|
@ -259,12 +260,12 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e
|
|||
// visibility entry for this status
|
||||
var visibility gtsmodel.Visibility
|
||||
|
||||
to, err := extractTos(statusable)
|
||||
to, err := ap.ExtractTos(statusable)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error extracting TO values: %s", err)
|
||||
}
|
||||
|
||||
cc, err := extractCCs(statusable)
|
||||
cc, err := ap.ExtractCCs(statusable)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error extracting CC values: %s", err)
|
||||
}
|
||||
|
|
@ -315,7 +316,7 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e
|
|||
return status, nil
|
||||
}
|
||||
|
||||
func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error) {
|
||||
func (c *converter) ASFollowToFollowRequest(followable ap.Followable) (*gtsmodel.FollowRequest, error) {
|
||||
|
||||
idProp := followable.GetJSONLDId()
|
||||
if idProp == nil || !idProp.IsIRI() {
|
||||
|
|
@ -323,7 +324,7 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo
|
|||
}
|
||||
uri := idProp.GetIRI().String()
|
||||
|
||||
origin, err := extractActor(followable)
|
||||
origin, err := ap.ExtractActor(followable)
|
||||
if err != nil {
|
||||
return nil, errors.New("error extracting actor property from follow")
|
||||
}
|
||||
|
|
@ -332,7 +333,7 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo
|
|||
return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
|
||||
}
|
||||
|
||||
target, err := extractObject(followable)
|
||||
target, err := ap.ExtractObject(followable)
|
||||
if err != nil {
|
||||
return nil, errors.New("error extracting object property from follow")
|
||||
}
|
||||
|
|
@ -350,14 +351,14 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo
|
|||
return followRequest, nil
|
||||
}
|
||||
|
||||
func (c *converter) ASFollowToFollow(followable Followable) (*gtsmodel.Follow, error) {
|
||||
func (c *converter) ASFollowToFollow(followable ap.Followable) (*gtsmodel.Follow, error) {
|
||||
idProp := followable.GetJSONLDId()
|
||||
if idProp == nil || !idProp.IsIRI() {
|
||||
return nil, errors.New("no id property set on follow, or was not an iri")
|
||||
}
|
||||
uri := idProp.GetIRI().String()
|
||||
|
||||
origin, err := extractActor(followable)
|
||||
origin, err := ap.ExtractActor(followable)
|
||||
if err != nil {
|
||||
return nil, errors.New("error extracting actor property from follow")
|
||||
}
|
||||
|
|
@ -366,7 +367,7 @@ func (c *converter) ASFollowToFollow(followable Followable) (*gtsmodel.Follow, e
|
|||
return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
|
||||
}
|
||||
|
||||
target, err := extractObject(followable)
|
||||
target, err := ap.ExtractObject(followable)
|
||||
if err != nil {
|
||||
return nil, errors.New("error extracting object property from follow")
|
||||
}
|
||||
|
|
@ -384,14 +385,14 @@ func (c *converter) ASFollowToFollow(followable Followable) (*gtsmodel.Follow, e
|
|||
return follow, nil
|
||||
}
|
||||
|
||||
func (c *converter) ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error) {
|
||||
func (c *converter) ASLikeToFave(likeable ap.Likeable) (*gtsmodel.StatusFave, error) {
|
||||
idProp := likeable.GetJSONLDId()
|
||||
if idProp == nil || !idProp.IsIRI() {
|
||||
return nil, errors.New("no id property set on like, or was not an iri")
|
||||
}
|
||||
uri := idProp.GetIRI().String()
|
||||
|
||||
origin, err := extractActor(likeable)
|
||||
origin, err := ap.ExtractActor(likeable)
|
||||
if err != nil {
|
||||
return nil, errors.New("error extracting actor property from like")
|
||||
}
|
||||
|
|
@ -400,7 +401,7 @@ func (c *converter) ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error
|
|||
return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
|
||||
}
|
||||
|
||||
target, err := extractObject(likeable)
|
||||
target, err := ap.ExtractObject(likeable)
|
||||
if err != nil {
|
||||
return nil, errors.New("error extracting object property from like")
|
||||
}
|
||||
|
|
@ -426,14 +427,14 @@ func (c *converter) ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (c *converter) ASBlockToBlock(blockable Blockable) (*gtsmodel.Block, error) {
|
||||
func (c *converter) ASBlockToBlock(blockable ap.Blockable) (*gtsmodel.Block, error) {
|
||||
idProp := blockable.GetJSONLDId()
|
||||
if idProp == nil || !idProp.IsIRI() {
|
||||
return nil, errors.New("ASBlockToBlock: no id property set on block, or was not an iri")
|
||||
}
|
||||
uri := idProp.GetIRI().String()
|
||||
|
||||
origin, err := extractActor(blockable)
|
||||
origin, err := ap.ExtractActor(blockable)
|
||||
if err != nil {
|
||||
return nil, errors.New("ASBlockToBlock: error extracting actor property from block")
|
||||
}
|
||||
|
|
@ -442,7 +443,7 @@ func (c *converter) ASBlockToBlock(blockable Blockable) (*gtsmodel.Block, error)
|
|||
return nil, fmt.Errorf("ASBlockToBlock: error extracting account with uri %s from the database: %s", origin.String(), err)
|
||||
}
|
||||
|
||||
target, err := extractObject(blockable)
|
||||
target, err := ap.ExtractObject(blockable)
|
||||
if err != nil {
|
||||
return nil, errors.New("ASBlockToBlock: error extracting object property from block")
|
||||
}
|
||||
|
|
@ -461,7 +462,7 @@ func (c *converter) ASBlockToBlock(blockable Blockable) (*gtsmodel.Block, error)
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Status, bool, error) {
|
||||
func (c *converter) ASAnnounceToStatus(announceable ap.Announceable) (*gtsmodel.Status, bool, error) {
|
||||
status := >smodel.Status{}
|
||||
isNew := true
|
||||
|
||||
|
|
@ -480,7 +481,7 @@ func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Sta
|
|||
status.URI = uri
|
||||
|
||||
// get the URI of the announced/boosted status
|
||||
boostedStatusURI, err := extractObject(announceable)
|
||||
boostedStatusURI, err := ap.ExtractObject(announceable)
|
||||
if err != nil {
|
||||
return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error getting object from announce: %s", err)
|
||||
}
|
||||
|
|
@ -491,7 +492,7 @@ func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Sta
|
|||
}
|
||||
|
||||
// get the published time for the announce
|
||||
published, err := extractPublished(announceable)
|
||||
published, err := ap.ExtractPublished(announceable)
|
||||
if err != nil {
|
||||
return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error extracting published time: %s", err)
|
||||
}
|
||||
|
|
@ -499,7 +500,7 @@ func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Sta
|
|||
status.UpdatedAt = published
|
||||
|
||||
// get the actor's IRI (ie., the person who boosted the status)
|
||||
actor, err := extractActor(announceable)
|
||||
actor, err := ap.ExtractActor(announceable)
|
||||
if err != nil {
|
||||
return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error extracting actor: %s", err)
|
||||
}
|
||||
|
|
@ -522,12 +523,12 @@ func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Sta
|
|||
// parse the visibility from the To and CC entries
|
||||
var visibility gtsmodel.Visibility
|
||||
|
||||
to, err := extractTos(announceable)
|
||||
to, err := ap.ExtractTos(announceable)
|
||||
if err != nil {
|
||||
return nil, isNew, fmt.Errorf("error extracting TO values: %s", err)
|
||||
}
|
||||
|
||||
cc, err := extractCCs(announceable)
|
||||
cc, err := ap.ExtractCCs(announceable)
|
||||
if err != nil {
|
||||
return nil, isNew, fmt.Errorf("error extracting CC values: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
|
@ -342,7 +343,7 @@ func (suite *ASToInternalTestSuite) SetupSuite() {
|
|||
}
|
||||
|
||||
func (suite *ASToInternalTestSuite) SetupTest() {
|
||||
testrig.StandardDBSetup(suite.db)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
}
|
||||
|
||||
func (suite *ASToInternalTestSuite) TestParsePerson() {
|
||||
|
|
@ -364,7 +365,7 @@ func (suite *ASToInternalTestSuite) TestParseGargron() {
|
|||
t, err := streams.ToType(context.Background(), m)
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
rep, ok := t.(typeutils.Accountable)
|
||||
rep, ok := t.(ap.Accountable)
|
||||
assert.True(suite.T(), ok)
|
||||
|
||||
acct, err := suite.typeconverter.ASRepresentationToAccount(rep, false)
|
||||
|
|
@ -391,7 +392,7 @@ func (suite *ASToInternalTestSuite) TestParseStatus() {
|
|||
first := obj.Begin()
|
||||
assert.NotNil(suite.T(), first)
|
||||
|
||||
rep, ok := first.GetType().(typeutils.Statusable)
|
||||
rep, ok := first.GetType().(ap.Statusable)
|
||||
assert.True(suite.T(), ok)
|
||||
|
||||
status, err := suite.typeconverter.ASStatusToStatus(rep)
|
||||
|
|
@ -418,7 +419,7 @@ func (suite *ASToInternalTestSuite) TestParseStatusWithMention() {
|
|||
first := obj.Begin()
|
||||
assert.NotNil(suite.T(), first)
|
||||
|
||||
rep, ok := first.GetType().(typeutils.Statusable)
|
||||
rep, ok := first.GetType().(ap.Statusable)
|
||||
assert.True(suite.T(), ok)
|
||||
|
||||
status, err := suite.typeconverter.ASStatusToStatus(rep)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@
|
|||
package typeutils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
|
|
@ -99,17 +102,17 @@ type TypeConverter interface {
|
|||
// If update is false, and the account is already known in the database, then the existing account entry will be returned.
|
||||
// If update is true, then even if the account is already known, all fields in the accountable will be parsed and a new *gtsmodel.Account
|
||||
// will be generated. This is useful when one needs to force refresh of an account, eg., during an Update of a Profile.
|
||||
ASRepresentationToAccount(accountable Accountable, update bool) (*gtsmodel.Account, error)
|
||||
ASRepresentationToAccount(accountable ap.Accountable, update bool) (*gtsmodel.Account, error)
|
||||
// ASStatus converts a remote activitystreams 'status' representation into a gts model status.
|
||||
ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error)
|
||||
ASStatusToStatus(statusable ap.Statusable) (*gtsmodel.Status, error)
|
||||
// ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request.
|
||||
ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error)
|
||||
ASFollowToFollowRequest(followable ap.Followable) (*gtsmodel.FollowRequest, error)
|
||||
// ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow.
|
||||
ASFollowToFollow(followable Followable) (*gtsmodel.Follow, error)
|
||||
ASFollowToFollow(followable ap.Followable) (*gtsmodel.Follow, error)
|
||||
// ASLikeToFave converts a remote activitystreams 'like' representation into a gts model status fave.
|
||||
ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error)
|
||||
ASLikeToFave(likeable ap.Likeable) (*gtsmodel.StatusFave, error)
|
||||
// ASBlockToBlock converts a remote activity streams 'block' representation into a gts model block.
|
||||
ASBlockToBlock(blockable Blockable) (*gtsmodel.Block, error)
|
||||
ASBlockToBlock(blockable ap.Blockable) (*gtsmodel.Block, error)
|
||||
// ASAnnounceToStatus converts an activitystreams 'announce' into a status.
|
||||
//
|
||||
// The returned bool indicates whether this status is new (true) or not new (false).
|
||||
|
|
@ -122,7 +125,7 @@ type TypeConverter interface {
|
|||
// This is useful when multiple users on an instance might receive the same boost, and we only want to process the boost once.
|
||||
//
|
||||
// NOTE -- this is different from one status being boosted multiple times! In this case, new boosts should indeed be created.
|
||||
ASAnnounceToStatus(announceable Announceable) (status *gtsmodel.Status, new bool, err error)
|
||||
ASAnnounceToStatus(announceable ap.Announceable) (status *gtsmodel.Status, new bool, err error)
|
||||
|
||||
/*
|
||||
INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL
|
||||
|
|
@ -150,7 +153,10 @@ type TypeConverter interface {
|
|||
BoostToAS(boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) (vocab.ActivityStreamsAnnounce, error)
|
||||
// BlockToAS converts a gts model block into an activityStreams BLOCK, suitable for federation.
|
||||
BlockToAS(block *gtsmodel.Block) (vocab.ActivityStreamsBlock, error)
|
||||
|
||||
// StatusToASRepliesCollection converts a gts model status into an activityStreams REPLIES collection.
|
||||
StatusToASRepliesCollection(status *gtsmodel.Status, onlyOtherAccounts bool) (vocab.ActivityStreamsCollection, error)
|
||||
// StatusURIsToASRepliesPage returns a collection page with appropriate next/part of pagination.
|
||||
StatusURIsToASRepliesPage(status *gtsmodel.Status, onlyOtherAccounts bool, minID string, replies map[string]*url.URL) (vocab.ActivityStreamsCollectionPage, error)
|
||||
/*
|
||||
INTERNAL (gts) MODEL TO INTERNAL MODEL
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ package typeutils_test
|
|||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
|
|
@ -34,7 +35,7 @@ type ConverterStandardTestSuite struct {
|
|||
db db.DB
|
||||
log *logrus.Logger
|
||||
accounts map[string]*gtsmodel.Account
|
||||
people map[string]typeutils.Accountable
|
||||
people map[string]ap.Accountable
|
||||
|
||||
typeconverter typeutils.TypeConverter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/go-fed/activity/streams"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
|
|
@ -505,7 +506,14 @@ func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, e
|
|||
status.SetActivityStreamsAttachment(attachmentProp)
|
||||
|
||||
// replies
|
||||
// TODO
|
||||
repliesCollection, err := c.StatusToASRepliesCollection(s, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating repliesCollection: %s", err)
|
||||
}
|
||||
|
||||
repliesProp := streams.NewActivityStreamsRepliesProperty()
|
||||
repliesProp.SetActivityStreamsCollection(repliesCollection)
|
||||
status.SetActivityStreamsReplies(repliesProp)
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
|
@ -850,3 +858,138 @@ func (c *converter) BlockToAS(b *gtsmodel.Block) (vocab.ActivityStreamsBlock, er
|
|||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
/*
|
||||
the goal is to end up with something like this:
|
||||
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies",
|
||||
"type": "Collection",
|
||||
"first": {
|
||||
"id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?page=true",
|
||||
"type": "CollectionPage",
|
||||
"next": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true",
|
||||
"partOf": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies",
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
*/
|
||||
func (c *converter) StatusToASRepliesCollection(status *gtsmodel.Status, onlyOtherAccounts bool) (vocab.ActivityStreamsCollection, error) {
|
||||
collectionID := fmt.Sprintf("%s/replies", status.URI)
|
||||
collectionIDURI, err := url.Parse(collectionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
collection := streams.NewActivityStreamsCollection()
|
||||
|
||||
// collection.id
|
||||
collectionIDProp := streams.NewJSONLDIdProperty()
|
||||
collectionIDProp.SetIRI(collectionIDURI)
|
||||
collection.SetJSONLDId(collectionIDProp)
|
||||
|
||||
// first
|
||||
first := streams.NewActivityStreamsFirstProperty()
|
||||
firstPage := streams.NewActivityStreamsCollectionPage()
|
||||
|
||||
// first.id
|
||||
firstPageIDProp := streams.NewJSONLDIdProperty()
|
||||
firstPageID, err := url.Parse(fmt.Sprintf("%s?page=true", collectionID))
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
firstPageIDProp.SetIRI(firstPageID)
|
||||
firstPage.SetJSONLDId(firstPageIDProp)
|
||||
|
||||
// first.next
|
||||
nextProp := streams.NewActivityStreamsNextProperty()
|
||||
nextPropID, err := url.Parse(fmt.Sprintf("%s?only_other_accounts=%t&page=true", collectionID, onlyOtherAccounts))
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
nextProp.SetIRI(nextPropID)
|
||||
firstPage.SetActivityStreamsNext(nextProp)
|
||||
|
||||
// first.partOf
|
||||
partOfProp := streams.NewActivityStreamsPartOfProperty()
|
||||
partOfProp.SetIRI(collectionIDURI)
|
||||
firstPage.SetActivityStreamsPartOf(partOfProp)
|
||||
|
||||
first.SetActivityStreamsCollectionPage(firstPage)
|
||||
|
||||
// collection.first
|
||||
collection.SetActivityStreamsFirst(first)
|
||||
|
||||
return collection, nil
|
||||
}
|
||||
|
||||
/*
|
||||
the goal is to end up with something like this:
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true",
|
||||
"type": "CollectionPage",
|
||||
"next": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?min_id=106720870266901180&only_other_accounts=true&page=true",
|
||||
"partOf": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies",
|
||||
"items": [
|
||||
"https://example.com/users/someone/statuses/106720752853216226",
|
||||
"https://somewhere.online/users/eeeeeeeeeep/statuses/106720870163727231"
|
||||
]
|
||||
}
|
||||
*/
|
||||
func (c *converter) StatusURIsToASRepliesPage(status *gtsmodel.Status, onlyOtherAccounts bool, minID string, replies map[string]*url.URL) (vocab.ActivityStreamsCollectionPage, error) {
|
||||
collectionID := fmt.Sprintf("%s/replies", status.URI)
|
||||
|
||||
page := streams.NewActivityStreamsCollectionPage()
|
||||
|
||||
// .id
|
||||
pageIDProp := streams.NewJSONLDIdProperty()
|
||||
pageIDString := fmt.Sprintf("%s?page=true&only_other_accounts=%t", collectionID, onlyOtherAccounts)
|
||||
if minID != "" {
|
||||
pageIDString = fmt.Sprintf("%s&min_id=%s", pageIDString, minID)
|
||||
}
|
||||
|
||||
pageID, err := url.Parse(pageIDString)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
pageIDProp.SetIRI(pageID)
|
||||
page.SetJSONLDId(pageIDProp)
|
||||
|
||||
// .partOf
|
||||
collectionIDURI, err := url.Parse(collectionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
partOfProp := streams.NewActivityStreamsPartOfProperty()
|
||||
partOfProp.SetIRI(collectionIDURI)
|
||||
page.SetActivityStreamsPartOf(partOfProp)
|
||||
|
||||
// .items
|
||||
items := streams.NewActivityStreamsItemsProperty()
|
||||
var highestID string
|
||||
for k, v := range replies {
|
||||
items.AppendIRI(v)
|
||||
if k > highestID {
|
||||
highestID = k
|
||||
}
|
||||
}
|
||||
page.SetActivityStreamsItems(items)
|
||||
|
||||
// .next
|
||||
nextProp := streams.NewActivityStreamsNextProperty()
|
||||
nextPropIDString := fmt.Sprintf("%s?only_other_accounts=%t&page=true", collectionID, onlyOtherAccounts)
|
||||
if highestID != "" {
|
||||
nextPropIDString = fmt.Sprintf("%s&min_id=%s", nextPropIDString, highestID)
|
||||
}
|
||||
|
||||
nextPropID, err := url.Parse(nextPropIDString)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
nextProp.SetIRI(nextPropID)
|
||||
page.SetActivityStreamsNext(nextProp)
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func (suite *InternalToASTestSuite) SetupSuite() {
|
|||
}
|
||||
|
||||
func (suite *InternalToASTestSuite) SetupTest() {
|
||||
testrig.StandardDBSetup(suite.db)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
}
|
||||
|
||||
// TearDownTest drops tables to make sure there's no data in the db
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue