hup hup hup allee
This commit is contained in:
parent
9e1f61c373
commit
8104a03bd5
|
@ -56,7 +56,7 @@ func (m *Module) UsersGETHandler(c *gin.Context) {
|
|||
|
||||
// make a copy of the context to pass along so we don't break anything
|
||||
cp := c.Copy()
|
||||
user, err := m.processor.GetAPUser(requestedUsername, cp.Request) // GetAPUser handles auth as well
|
||||
user, err := m.processor.GetFediUser(requestedUsername, cp.Request) // GetAPUser handles auth as well
|
||||
if err != nil {
|
||||
l.Info(err.Error())
|
||||
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
||||
|
|
|
@ -234,50 +234,3 @@ func (f *federator) GetTransportForUser(username string) (pub.Transport, error)
|
|||
}
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
const (
|
||||
activityStreamsContext = "https://www.w3.org/ns/activitystreams"
|
||||
w3idContext = "https://w3id.org/security/v1"
|
||||
tootContext = "http://joinmastodon.org/ns#"
|
||||
schemaContext = "http://schema.org#"
|
||||
)
|
||||
|
||||
// ActivityStreamsContext returns the url representation of https://www.w3.org/ns/activitystreams
|
||||
func ActivityStreamsContext() *url.URL {
|
||||
u, err := url.Parse(activityStreamsContext)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// W3IDContext returns the url representation of https://w3id.org/security/v1
|
||||
func W3IDContext() *url.URL {
|
||||
u, err := url.Parse(w3idContext)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// TootContext returns the url representation of http://joinmastodon.org/ns#
|
||||
func TootContext() *url.URL {
|
||||
u, err := url.Parse(tootContext)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// SchemaContext returns the url representation of http://schema.org#
|
||||
func SchemaContext() *url.URL {
|
||||
u, err := url.Parse(schemaContext)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func StandardContexts() vocab.ActivityStreamsContextProperty {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
package message
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
func (p *processor) GetAPUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) {
|
||||
// get the account the request is referring to
|
||||
requestedAccount := >smodel.Account{}
|
||||
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
||||
return nil, NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err))
|
||||
}
|
||||
|
||||
// authenticate the request
|
||||
requestingAccountURI, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request)
|
||||
if err != nil {
|
||||
return nil, NewErrorNotAuthorized(err)
|
||||
}
|
||||
|
||||
requestingAccount := >smodel.Account{}
|
||||
err = p.db.GetWhere("uri", requestingAccountURI.String(), requestingAccount)
|
||||
if err != nil {
|
||||
if _, ok := err.(db.ErrNoEntries); !ok {
|
||||
// we don't have an entry for this account yet
|
||||
// what we do now should depend on our chosen federation method
|
||||
// for now though, we'll just dereference it
|
||||
// TODO: slow-fed
|
||||
requestingPerson, err := p.federator.DereferenceRemoteAccount(requestedUsername, requestingAccountURI)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
requestedAccount, err = p.tc.ASPersonToAccount(requestingPerson)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
if err := p.db.Put(requestingAccount); err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
} else {
|
||||
// something has actually gone wrong
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if blocked {
|
||||
return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
|
||||
}
|
||||
|
||||
requestedPerson, err := p.tc.AccountToAS(requestedAccount)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
data, err := streams.Serialize(requestedPerson)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package message
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// authenticateAndDereferenceFediRequest authenticates the HTTP signature of an incoming federation request, using the given
|
||||
// username to perform the validation. It will *also* dereference the originator of the request and return it as a gtsmodel account
|
||||
// for further processing. NOTE that this function will have the side effect of putting the dereferenced account into the database,
|
||||
// and passing it into the processor through a channel for further asynchronous processing.
|
||||
func (p *processor) authenticateAndDereferenceFediRequest(username string, r *http.Request) (*gtsmodel.Account, error) {
|
||||
|
||||
// first authenticate
|
||||
requestingAccountURI, err := p.federator.AuthenticateFederatedRequest(username, r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't authenticate request for username %s: %s", username, err)
|
||||
}
|
||||
|
||||
// OK now we can do the dereferencing part
|
||||
// we might already have an entry for this account so check that first
|
||||
requestingAccount := >smodel.Account{}
|
||||
|
||||
err = p.db.GetWhere("uri", requestingAccountURI.String(), requestingAccount)
|
||||
if err == nil {
|
||||
// we do have it yay, return it
|
||||
return requestingAccount, nil
|
||||
}
|
||||
|
||||
if _, ok := err.(db.ErrNoEntries); !ok {
|
||||
// something has actually gone wrong so bail
|
||||
return nil, fmt.Errorf("database error getting account with uri %s: %s", requestingAccountURI.String(), err)
|
||||
}
|
||||
|
||||
// we just don't have an entry for this account yet
|
||||
// what we do now should depend on our chosen federation method
|
||||
// for now though, we'll just dereference it
|
||||
// TODO: slow-fed
|
||||
requestingPerson, err := p.federator.DereferenceRemoteAccount(username, requestingAccountURI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't dereference %s: %s", requestingAccountURI.String(), err)
|
||||
}
|
||||
|
||||
// convert it to our internal account representation
|
||||
requestingAccount, err = p.tc.ASPersonToAccount(requestingPerson)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't convert dereferenced uri %s to gtsmodel account: %s", requestingAccountURI.String(), err)
|
||||
}
|
||||
|
||||
// shove it in the database for later
|
||||
if err := p.db.Put(requestingAccount); err != nil {
|
||||
return nil, fmt.Errorf("database error inserting account with uri %s: %s", requestingAccountURI.String(), err)
|
||||
}
|
||||
|
||||
// put it in our channel to queue it for async processing
|
||||
p.FromFederator() <- FromFederator{
|
||||
APObjectType: gtsmodel.ActivityStreamsProfile,
|
||||
APActivityType: gtsmodel.ActivityStreamsCreate,
|
||||
Activity: requestingAccount,
|
||||
}
|
||||
|
||||
return requestingAccount, nil
|
||||
}
|
||||
|
||||
func (p *processor) GetFediUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) {
|
||||
// get the account the request is referring to
|
||||
requestedAccount := >smodel.Account{}
|
||||
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
||||
return nil, NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err))
|
||||
}
|
||||
|
||||
// authenticate the request
|
||||
requestingAccount, err := p.authenticateAndDereferenceFediRequest(requestedUsername, request)
|
||||
if err != nil {
|
||||
return nil, NewErrorNotAuthorized(err)
|
||||
}
|
||||
|
||||
blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if blocked {
|
||||
return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
|
||||
}
|
||||
|
||||
requestedPerson, err := p.tc.AccountToAS(requestedAccount)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
data, err := streams.Serialize(requestedPerson)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
|
@ -46,8 +46,12 @@ type Processor interface {
|
|||
FromClientAPI() chan FromClientAPI
|
||||
// ToFederator returns a channel for putting in messages that need to go to the federator (activitypub).
|
||||
ToFederator() chan ToFederator
|
||||
// FromFederator returns a channel for putting messages in that come from the federator going into the processor
|
||||
// FromFederator returns a channel for putting messages in that come from the federator (activitypub) going into the processor
|
||||
FromFederator() chan FromFederator
|
||||
// Start starts the Processor, reading from its channels and passing messages back and forth.
|
||||
Start() error
|
||||
// Stop stops the processor cleanly, finishing handling any remaining messages before closing down.
|
||||
Stop() error
|
||||
|
||||
/*
|
||||
CLIENT API-FACING PROCESSING FUNCTIONS
|
||||
|
@ -80,6 +84,7 @@ type Processor interface {
|
|||
|
||||
// MediaCreate handles the creation of a media attachment, using the given form.
|
||||
MediaCreate(authed *oauth.Auth, form *apimodel.AttachmentRequest) (*apimodel.Attachment, error)
|
||||
// MediaGet handles the fetching of a media attachment, using the given request form.
|
||||
MediaGet(authed *oauth.Auth, form *apimodel.GetContentRequestForm) (*apimodel.Content, error)
|
||||
// AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form.
|
||||
AdminEmojiCreate(authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error)
|
||||
|
@ -92,12 +97,12 @@ type Processor interface {
|
|||
response, pass work to the processor using a channel instead.
|
||||
*/
|
||||
|
||||
GetAPUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode)
|
||||
// GetFediUser handles the getting of a fedi/activitypub representation of a user/account, performing appropriate authentication
|
||||
// before returning a JSON serializable interface to the caller.
|
||||
GetFediUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode)
|
||||
|
||||
|
||||
|
||||
// Start starts the Processor, reading from its channels and passing messages back and forth.
|
||||
Start() error
|
||||
// Stop stops the processor cleanly, finishing handling any remaining messages before closing down.
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// processor just implements the Processor interface
|
||||
|
@ -161,8 +166,12 @@ func (p *processor) Start() error {
|
|||
select {
|
||||
case clientMsg := <-p.toClientAPI:
|
||||
p.log.Infof("received message TO client API: %+v", clientMsg)
|
||||
case clientMsg := <-p.fromClientAPI:
|
||||
p.log.Infof("received message FROM client API: %+v", clientMsg)
|
||||
case federatorMsg := <-p.toFederator:
|
||||
p.log.Infof("received message TO federator: %+v", federatorMsg)
|
||||
case federatorMsg := <-p.fromFederator:
|
||||
p.log.Infof("received message FROM federator: %+v", federatorMsg)
|
||||
case <-p.stop:
|
||||
break DistLoop
|
||||
}
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
)
|
||||
|
||||
type usernameable interface {
|
||||
GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty
|
||||
}
|
||||
|
||||
type iconable interface {
|
||||
GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty
|
||||
}
|
||||
|
||||
type displaynameable interface {
|
||||
GetActivityStreamsName() vocab.ActivityStreamsNameProperty
|
||||
}
|
||||
|
||||
type imageable interface {
|
||||
GetActivityStreamsImage() vocab.ActivityStreamsImageProperty
|
||||
}
|
||||
|
||||
func extractPreferredUsername(i usernameable) (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 displaynameable) (string, error) {
|
||||
nameProp := i.GetActivityStreamsName()
|
||||
if nameProp == nil {
|
||||
return "", errors.New("activityStreamsName not found")
|
||||
}
|
||||
|
||||
// take the first name string we can find
|
||||
for nameIter := nameProp.Begin(); nameIter != nameProp.End(); nameIter = nameIter.Next() {
|
||||
if nameIter.IsXMLSchemaString() && nameIter.GetXMLSchemaString() != "" {
|
||||
return nameIter.GetXMLSchemaString(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("activityStreamsName not found")
|
||||
}
|
||||
|
||||
// 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 iconable) (*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. is a supported type
|
||||
// 3. has a URL so we can grab it
|
||||
for iconIter := iconProp.Begin(); iconIter != iconProp.End(); iconIter = iconIter.Next() {
|
||||
// 1. is an image
|
||||
if !iconIter.IsActivityStreamsImage() {
|
||||
continue
|
||||
}
|
||||
imageValue := iconIter.GetActivityStreamsImage()
|
||||
if imageValue == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. is a supported type
|
||||
imageType := imageValue.GetActivityStreamsMediaType()
|
||||
if imageType == nil || !media.SupportedImageType(imageType.Get()) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. has a URL so we can grab it
|
||||
imageURLProp := imageValue.GetActivityStreamsUrl()
|
||||
if imageURLProp == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// URL is also an iterable!
|
||||
// so let's take the first valid one we can find
|
||||
for urlIter := imageURLProp.Begin(); urlIter != imageURLProp.End(); urlIter = urlIter.Next() {
|
||||
if !urlIter.IsIRI() {
|
||||
continue
|
||||
}
|
||||
if urlIter.GetIRI() == nil {
|
||||
continue
|
||||
}
|
||||
// found it!!!
|
||||
return urlIter.GetIRI(), 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 imageable) (*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. is a supported type
|
||||
// 3. has a URL so we can grab it
|
||||
for imageIter := imageProp.Begin(); imageIter != imageProp.End(); imageIter = imageIter.Next() {
|
||||
// 1. is an image
|
||||
if !imageIter.IsActivityStreamsImage() {
|
||||
continue
|
||||
}
|
||||
imageValue := imageIter.GetActivityStreamsImage()
|
||||
if imageValue == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. is a supported type
|
||||
imageType := imageValue.GetActivityStreamsMediaType()
|
||||
if imageType == nil || !media.SupportedImageType(imageType.Get()) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. has a URL so we can grab it
|
||||
imageURLProp := imageValue.GetActivityStreamsUrl()
|
||||
if imageURLProp == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// URL is also an iterable!
|
||||
// so let's take the first valid one we can find
|
||||
for urlIter := imageURLProp.Begin(); urlIter != imageURLProp.End(); urlIter = urlIter.Next() {
|
||||
if !urlIter.IsIRI() {
|
||||
continue
|
||||
}
|
||||
if urlIter.GetIRI() == nil {
|
||||
continue
|
||||
}
|
||||
// found it!!!
|
||||
return urlIter.GetIRI(), 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")
|
||||
}
|
|
@ -1,14 +1,30 @@
|
|||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
)
|
||||
|
||||
func (c *converter) ASPersonToAccount(person vocab.ActivityStreamsPerson) (*gtsmodel.Account, error) {
|
||||
|
@ -34,16 +50,15 @@ func (c *converter) ASPersonToAccount(person vocab.ActivityStreamsPerson) (*gtsm
|
|||
acct = >smodel.Account{}
|
||||
acct.URI = uri.String()
|
||||
|
||||
// Username
|
||||
// Username aka preferredUsername
|
||||
// We need this one so bail if it's not set.
|
||||
username, err := extractUsername(person)
|
||||
username, err := extractPreferredUsername(person)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't extract username: %s", err)
|
||||
}
|
||||
acct.Username = username
|
||||
|
||||
// Domain
|
||||
// We need this one as well
|
||||
acct.Domain = uri.Host
|
||||
|
||||
// avatar aka icon
|
||||
|
@ -58,140 +73,54 @@ func (c *converter) ASPersonToAccount(person vocab.ActivityStreamsPerson) (*gtsm
|
|||
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(person); err == nil {
|
||||
acct.DisplayName = displayName
|
||||
}
|
||||
|
||||
// fields aka attachment array
|
||||
// TODO
|
||||
|
||||
// note aka summary
|
||||
// TODO
|
||||
|
||||
// bot
|
||||
// TODO: parse this from application vs. person type
|
||||
|
||||
// locked aka manuallyApprovesFollowers
|
||||
// TODO
|
||||
|
||||
// discoverable
|
||||
// TODO
|
||||
|
||||
// url property
|
||||
// TODO
|
||||
|
||||
// InboxURI
|
||||
// TODO
|
||||
|
||||
// OutboxURI
|
||||
// TODO
|
||||
|
||||
// FollowingURI
|
||||
// TODO
|
||||
|
||||
// FollowersURI
|
||||
// TODO
|
||||
|
||||
// FeaturedURI
|
||||
// TODO
|
||||
|
||||
// FeaturedTagsURI
|
||||
// TODO
|
||||
|
||||
// alsoKnownAs
|
||||
// TODO
|
||||
|
||||
// publicKey
|
||||
// TODO
|
||||
|
||||
return acct, nil
|
||||
}
|
||||
|
||||
type usernameable interface {
|
||||
GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty
|
||||
}
|
||||
|
||||
func extractUsername(i usernameable) (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
|
||||
}
|
||||
|
||||
type iconable interface {
|
||||
GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty
|
||||
}
|
||||
|
||||
// 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 iconable) (*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. is a supported type
|
||||
// 3. has a URL so we can grab it
|
||||
for iconIter := iconProp.Begin(); iconIter != iconProp.End(); iconIter = iconIter.Next() {
|
||||
// 1. is an image
|
||||
if !iconIter.IsActivityStreamsImage() {
|
||||
continue
|
||||
}
|
||||
imageValue := iconIter.GetActivityStreamsImage()
|
||||
if imageValue == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. is a supported type
|
||||
imageType := imageValue.GetActivityStreamsMediaType()
|
||||
if imageType == nil || !media.SupportedImageType(imageType.Get()) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. has a URL so we can grab it
|
||||
imageURLProp := imageValue.GetActivityStreamsUrl()
|
||||
if imageURLProp == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// URL is also an iterable!
|
||||
// so let's take the first valid one we can find
|
||||
for urlIter := imageURLProp.Begin(); urlIter != imageURLProp.End(); urlIter = urlIter.Next() {
|
||||
if !urlIter.IsIRI() {
|
||||
continue
|
||||
}
|
||||
if urlIter.GetIRI() == nil {
|
||||
continue
|
||||
}
|
||||
// found it!!!
|
||||
return urlIter.GetIRI(), 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")
|
||||
}
|
||||
|
||||
type imageable interface {
|
||||
GetActivityStreamsImage() vocab.ActivityStreamsImageProperty
|
||||
}
|
||||
|
||||
// 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 imageable) (*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. is a supported type
|
||||
// 3. has a URL so we can grab it
|
||||
for imageIter := imageProp.Begin(); imageIter != imageProp.End(); imageIter = imageIter.Next() {
|
||||
// 1. is an image
|
||||
if !imageIter.IsActivityStreamsImage() {
|
||||
continue
|
||||
}
|
||||
imageValue := imageIter.GetActivityStreamsImage()
|
||||
if imageValue == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. is a supported type
|
||||
imageType := imageValue.GetActivityStreamsMediaType()
|
||||
if imageType == nil || !media.SupportedImageType(imageType.Get()) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. has a URL so we can grab it
|
||||
imageURLProp := imageValue.GetActivityStreamsUrl()
|
||||
if imageURLProp == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// URL is also an iterable!
|
||||
// so let's take the first valid one we can find
|
||||
for urlIter := imageURLProp.Begin(); urlIter != imageURLProp.End(); urlIter = urlIter.Next() {
|
||||
if !urlIter.IsIRI() {
|
||||
continue
|
||||
}
|
||||
if urlIter.GetIRI() == nil {
|
||||
continue
|
||||
}
|
||||
// found it!!!
|
||||
return urlIter.GetIRI(), 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")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
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_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type ASToInternalTestSuite struct {
|
||||
ConverterStandardTestSuite
|
||||
}
|
||||
|
||||
func (suite *ASToInternalTestSuite) SetupSuite() {
|
||||
suite.config = testrig.NewTestConfig()
|
||||
suite.db = testrig.NewTestDB()
|
||||
suite.log = testrig.NewTestLog()
|
||||
suite.accounts = testrig.NewTestAccounts()
|
||||
suite.people = testrig.NewTestFediPeople()
|
||||
suite.typeconverter = typeutils.NewConverter(suite.config, suite.db)
|
||||
}
|
||||
|
||||
func (suite *ASToInternalTestSuite) SetupTest() {
|
||||
testrig.StandardDBSetup(suite.db)
|
||||
}
|
||||
|
||||
func (suite *ASToInternalTestSuite) TestASPersonToAccount() {
|
||||
|
||||
testPerson := suite.people["new_person_1"]
|
||||
|
||||
acct, err := suite.typeconverter.ASPersonToAccount(testPerson)
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
fmt.Printf("%+v", acct)
|
||||
// TODO: write assertions here, rn we're just eyeballing the output
|
||||
|
||||
}
|
||||
|
||||
func (suite *ASToInternalTestSuite) TearDownTest() {
|
||||
testrig.StandardDBTeardown(suite.db)
|
||||
}
|
||||
|
||||
func TestASToInternalTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ASToInternalTestSuite))
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
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_test
|
||||
|
||||
import (
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
)
|
||||
|
||||
type ConverterStandardTestSuite struct {
|
||||
suite.Suite
|
||||
config *config.Config
|
||||
db db.DB
|
||||
log *logrus.Logger
|
||||
accounts map[string]*gtsmodel.Account
|
||||
people map[string]vocab.ActivityStreamsPerson
|
||||
|
||||
typeconverter typeutils.TypeConverter
|
||||
}
|
|
@ -230,9 +230,9 @@ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerso
|
|||
// image
|
||||
// Used as profile header.
|
||||
if a.HeaderMediaAttachmentID != "" {
|
||||
iconProperty := streams.NewActivityStreamsIconProperty()
|
||||
headerProperty := streams.NewActivityStreamsImageProperty()
|
||||
|
||||
iconImage := streams.NewActivityStreamsImage()
|
||||
headerImage := streams.NewActivityStreamsImage()
|
||||
|
||||
header := >smodel.MediaAttachment{}
|
||||
if err := c.db.GetByID(a.HeaderMediaAttachmentID, header); err != nil {
|
||||
|
@ -241,7 +241,7 @@ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerso
|
|||
|
||||
mediaType := streams.NewActivityStreamsMediaTypeProperty()
|
||||
mediaType.Set(header.File.ContentType)
|
||||
iconImage.SetActivityStreamsMediaType(mediaType)
|
||||
headerImage.SetActivityStreamsMediaType(mediaType)
|
||||
|
||||
headerURLProperty := streams.NewActivityStreamsUrlProperty()
|
||||
headerURL, err := url.Parse(header.URL)
|
||||
|
@ -249,9 +249,9 @@ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerso
|
|||
return nil, err
|
||||
}
|
||||
headerURLProperty.AppendIRI(headerURL)
|
||||
iconImage.SetActivityStreamsUrl(headerURLProperty)
|
||||
headerImage.SetActivityStreamsUrl(headerURLProperty)
|
||||
|
||||
iconProperty.AppendActivityStreamsImage(iconImage)
|
||||
headerProperty.AppendActivityStreamsImage(headerImage)
|
||||
}
|
||||
|
||||
return person, nil
|
||||
|
|
|
@ -24,25 +24,15 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type InternalToASTestSuite struct {
|
||||
suite.Suite
|
||||
config *config.Config
|
||||
db db.DB
|
||||
log *logrus.Logger
|
||||
accounts map[string]*gtsmodel.Account
|
||||
|
||||
typeconverter typeutils.TypeConverter
|
||||
ConverterStandardTestSuite
|
||||
}
|
||||
|
||||
// SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout
|
||||
|
@ -52,6 +42,7 @@ func (suite *InternalToASTestSuite) SetupSuite() {
|
|||
suite.db = testrig.NewTestDB()
|
||||
suite.log = testrig.NewTestLog()
|
||||
suite.accounts = testrig.NewTestAccounts()
|
||||
suite.people = testrig.NewTestFediPeople()
|
||||
suite.typeconverter = typeutils.NewConverter(suite.config, suite.db)
|
||||
}
|
||||
|
||||
|
@ -64,7 +55,7 @@ func (suite *InternalToASTestSuite) TearDownTest() {
|
|||
testrig.StandardDBTeardown(suite.db)
|
||||
}
|
||||
|
||||
func (suite *InternalToASTestSuite) TestPostAccountToAS() {
|
||||
func (suite *InternalToASTestSuite) TestAccountToAS() {
|
||||
testAccount := suite.accounts["local_account_1"] // take zork for this test
|
||||
|
||||
asPerson, err := suite.typeconverter.AccountToAS(testAccount)
|
||||
|
|
|
@ -24,7 +24,9 @@ import (
|
|||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -1047,6 +1049,37 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit
|
|||
}
|
||||
}
|
||||
|
||||
// NewTestFediPeople returns a bunch of activity pub Person representations for testing converters and so on.
|
||||
func NewTestFediPeople() map[string]vocab.ActivityStreamsPerson {
|
||||
new_person_1priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
new_person_1pub := &new_person_1priv.PublicKey
|
||||
|
||||
return map[string]vocab.ActivityStreamsPerson{
|
||||
"new_person_1": newPerson(
|
||||
URLMustParse("https://unknown-instance.com/users/brand_new_person"),
|
||||
URLMustParse("https://unknown-instance.com/users/brand_new_person/following"),
|
||||
URLMustParse("https://unknown-instance.com/users/brand_new_person/followers"),
|
||||
URLMustParse("https://unknown-instance.com/users/brand_new_person/inbox"),
|
||||
URLMustParse("https://unknown-instance.com/users/brand_new_person/outbox"),
|
||||
URLMustParse("https://unknown-instance.com/users/brand_new_person/collections/featured"),
|
||||
"brand_new_person",
|
||||
"Geoff Brando New Personson",
|
||||
"hey I'm a new person, your instance hasn't seen me yet uwu",
|
||||
URLMustParse("https://unknown-instance.com/@brand_new_person"),
|
||||
true,
|
||||
URLMustParse("https://unknown-instance.com/users/brand_new_person#main-key"),
|
||||
new_person_1pub,
|
||||
URLMustParse("https://unknown-instance.com/media/some_avatar_filename.jpeg"),
|
||||
"image/jpeg",
|
||||
URLMustParse("https://unknown-instance.com/media/some_header_filename.jpeg"),
|
||||
"image/png",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func NewTestDereferenceRequests(accounts map[string]*gtsmodel.Account) map[string]ActivityWithSignature {
|
||||
sig, digest, date := getSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].URI))
|
||||
return map[string]ActivityWithSignature{
|
||||
|
@ -1134,6 +1167,186 @@ func getSignatureForDereference(pubKeyID string, privkey crypto.PrivateKey, dest
|
|||
return
|
||||
}
|
||||
|
||||
func newPerson(
|
||||
profileIDURI *url.URL,
|
||||
followingURI *url.URL,
|
||||
followersURI *url.URL,
|
||||
inboxURI *url.URL,
|
||||
outboxURI *url.URL,
|
||||
featuredURI *url.URL,
|
||||
username string,
|
||||
displayName string,
|
||||
note string,
|
||||
profileURL *url.URL,
|
||||
discoverable bool,
|
||||
publicKeyURI *url.URL,
|
||||
pkey *rsa.PublicKey,
|
||||
avatarURL *url.URL,
|
||||
avatarContentType string,
|
||||
headerURL *url.URL,
|
||||
headerContentType string) vocab.ActivityStreamsPerson {
|
||||
person := streams.NewActivityStreamsPerson()
|
||||
|
||||
// id should be the activitypub URI of this user
|
||||
// something like https://example.org/users/example_user
|
||||
idProp := streams.NewJSONLDIdProperty()
|
||||
idProp.SetIRI(profileIDURI)
|
||||
person.SetJSONLDId(idProp)
|
||||
|
||||
// following
|
||||
// The URI for retrieving a list of accounts this user is following
|
||||
followingProp := streams.NewActivityStreamsFollowingProperty()
|
||||
followingProp.SetIRI(followingURI)
|
||||
person.SetActivityStreamsFollowing(followingProp)
|
||||
|
||||
// followers
|
||||
// The URI for retrieving a list of this user's followers
|
||||
followersProp := streams.NewActivityStreamsFollowersProperty()
|
||||
followersProp.SetIRI(followersURI)
|
||||
person.SetActivityStreamsFollowers(followersProp)
|
||||
|
||||
// inbox
|
||||
// the activitypub inbox of this user for accepting messages
|
||||
inboxProp := streams.NewActivityStreamsInboxProperty()
|
||||
inboxProp.SetIRI(inboxURI)
|
||||
person.SetActivityStreamsInbox(inboxProp)
|
||||
|
||||
// outbox
|
||||
// the activitypub outbox of this user for serving messages
|
||||
outboxProp := streams.NewActivityStreamsOutboxProperty()
|
||||
outboxProp.SetIRI(outboxURI)
|
||||
person.SetActivityStreamsOutbox(outboxProp)
|
||||
|
||||
// featured posts
|
||||
// Pinned posts.
|
||||
featuredProp := streams.NewTootFeaturedProperty()
|
||||
featuredProp.SetIRI(featuredURI)
|
||||
person.SetTootFeatured(featuredProp)
|
||||
|
||||
// featuredTags
|
||||
// NOT IMPLEMENTED
|
||||
|
||||
// preferredUsername
|
||||
// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
|
||||
preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty()
|
||||
preferredUsernameProp.SetXMLSchemaString(username)
|
||||
person.SetActivityStreamsPreferredUsername(preferredUsernameProp)
|
||||
|
||||
// name
|
||||
// Used as profile display name.
|
||||
nameProp := streams.NewActivityStreamsNameProperty()
|
||||
if displayName != "" {
|
||||
nameProp.AppendXMLSchemaString(displayName)
|
||||
} else {
|
||||
nameProp.AppendXMLSchemaString(username)
|
||||
}
|
||||
person.SetActivityStreamsName(nameProp)
|
||||
|
||||
// summary
|
||||
// Used as profile bio.
|
||||
if note != "" {
|
||||
summaryProp := streams.NewActivityStreamsSummaryProperty()
|
||||
summaryProp.AppendXMLSchemaString(note)
|
||||
person.SetActivityStreamsSummary(summaryProp)
|
||||
}
|
||||
|
||||
// url
|
||||
// Used as profile link.
|
||||
urlProp := streams.NewActivityStreamsUrlProperty()
|
||||
urlProp.AppendIRI(profileURL)
|
||||
person.SetActivityStreamsUrl(urlProp)
|
||||
|
||||
// manuallyApprovesFollowers
|
||||
// Will be shown as a locked account.
|
||||
// TODO: NOT IMPLEMENTED **YET** -- this needs to be added as an activitypub extension to https://github.com/go-fed/activity, see https://github.com/go-fed/activity/tree/master/astool
|
||||
|
||||
// discoverable
|
||||
// Will be shown in the profile directory.
|
||||
discoverableProp := streams.NewTootDiscoverableProperty()
|
||||
discoverableProp.Set(discoverable)
|
||||
person.SetTootDiscoverable(discoverableProp)
|
||||
|
||||
// devices
|
||||
// NOT IMPLEMENTED, probably won't implement
|
||||
|
||||
// alsoKnownAs
|
||||
// Required for Move activity.
|
||||
// TODO: NOT IMPLEMENTED **YET** -- this needs to be added as an activitypub extension to https://github.com/go-fed/activity, see https://github.com/go-fed/activity/tree/master/astool
|
||||
|
||||
// publicKey
|
||||
// Required for signatures.
|
||||
publicKeyProp := streams.NewW3IDSecurityV1PublicKeyProperty()
|
||||
|
||||
// create the public key
|
||||
publicKey := streams.NewW3IDSecurityV1PublicKey()
|
||||
|
||||
// set ID for the public key
|
||||
publicKeyIDProp := streams.NewJSONLDIdProperty()
|
||||
publicKeyIDProp.SetIRI(publicKeyURI)
|
||||
publicKey.SetJSONLDId(publicKeyIDProp)
|
||||
|
||||
// set owner for the public key
|
||||
publicKeyOwnerProp := streams.NewW3IDSecurityV1OwnerProperty()
|
||||
publicKeyOwnerProp.SetIRI(profileIDURI)
|
||||
publicKey.SetW3IDSecurityV1Owner(publicKeyOwnerProp)
|
||||
|
||||
// set the pem key itself
|
||||
encodedPublicKey, err := x509.MarshalPKIXPublicKey(pkey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
publicKeyBytes := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Bytes: encodedPublicKey,
|
||||
})
|
||||
publicKeyPEMProp := streams.NewW3IDSecurityV1PublicKeyPemProperty()
|
||||
publicKeyPEMProp.Set(string(publicKeyBytes))
|
||||
publicKey.SetW3IDSecurityV1PublicKeyPem(publicKeyPEMProp)
|
||||
|
||||
// append the public key to the public key property
|
||||
publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey)
|
||||
|
||||
// set the public key property on the Person
|
||||
person.SetW3IDSecurityV1PublicKey(publicKeyProp)
|
||||
|
||||
// tag
|
||||
// TODO: Any tags used in the summary of this profile
|
||||
|
||||
// attachment
|
||||
// Used for profile fields.
|
||||
// TODO: The PropertyValue type has to be added: https://schema.org/PropertyValue
|
||||
|
||||
// endpoints
|
||||
// NOT IMPLEMENTED -- this is for shared inbox which we don't use
|
||||
|
||||
// icon
|
||||
// Used as profile avatar.
|
||||
iconProperty := streams.NewActivityStreamsIconProperty()
|
||||
iconImage := streams.NewActivityStreamsImage()
|
||||
mediaType := streams.NewActivityStreamsMediaTypeProperty()
|
||||
mediaType.Set(avatarContentType)
|
||||
iconImage.SetActivityStreamsMediaType(mediaType)
|
||||
avatarURLProperty := streams.NewActivityStreamsUrlProperty()
|
||||
avatarURLProperty.AppendIRI(avatarURL)
|
||||
iconImage.SetActivityStreamsUrl(avatarURLProperty)
|
||||
iconProperty.AppendActivityStreamsImage(iconImage)
|
||||
person.SetActivityStreamsIcon(iconProperty)
|
||||
|
||||
// image
|
||||
// Used as profile header.
|
||||
headerProperty := streams.NewActivityStreamsImageProperty()
|
||||
headerImage := streams.NewActivityStreamsImage()
|
||||
headerMediaType := streams.NewActivityStreamsMediaTypeProperty()
|
||||
mediaType.Set(headerContentType)
|
||||
headerImage.SetActivityStreamsMediaType(headerMediaType)
|
||||
headerURLProperty := streams.NewActivityStreamsUrlProperty()
|
||||
headerURLProperty.AppendIRI(headerURL)
|
||||
headerImage.SetActivityStreamsUrl(headerURLProperty)
|
||||
headerProperty.AppendActivityStreamsImage(headerImage)
|
||||
|
||||
return person
|
||||
}
|
||||
|
||||
// newNote returns a new activity streams note for the given parameters
|
||||
func newNote(
|
||||
noteID *url.URL,
|
||||
|
|
Loading…
Reference in New Issue