hup hup hup allee

This commit is contained in:
tsmethurst 2021-05-07 15:10:47 +02:00
commit 8104a03bd5
12 changed files with 697 additions and 281 deletions

View file

@ -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")
}

View file

@ -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 = &gtsmodel.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")
}

View file

@ -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))
}

View file

@ -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
}

View file

@ -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 := &gtsmodel.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

View file

@ -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)