summaryrefslogtreecommitdiff
path: root/internal/typeutils
diff options
context:
space:
mode:
Diffstat (limited to 'internal/typeutils')
-rw-r--r--internal/typeutils/converter.go10
-rw-r--r--internal/typeutils/defaulticons.go138
-rw-r--r--internal/typeutils/internaltofrontend.go3
-rw-r--r--internal/typeutils/internaltofrontend_test.go4
4 files changed, 151 insertions, 4 deletions
diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go
index 6996599ae..3effb9388 100644
--- a/internal/typeutils/converter.go
+++ b/internal/typeutils/converter.go
@@ -21,6 +21,7 @@ package typeutils
import (
"context"
"net/url"
+ "sync"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
@@ -195,10 +196,15 @@ type TypeConverter interface {
}
type converter struct {
- db db.DB
+ db db.DB
+ defaultAvatars []string
+ randAvatars sync.Map
}
// NewConverter returns a new Converter
func NewConverter(db db.DB) TypeConverter {
- return &converter{db: db}
+ return &converter{
+ db: db,
+ defaultAvatars: populateDefaultAvatars(),
+ }
}
diff --git a/internal/typeutils/defaulticons.go b/internal/typeutils/defaulticons.go
new file mode 100644
index 000000000..b2a9858af
--- /dev/null
+++ b/internal/typeutils/defaulticons.go
@@ -0,0 +1,138 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 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 (
+ "io/ioutil"
+ "math/rand"
+ "path/filepath"
+ "strings"
+
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+)
+
+const defaultHeaderPath = "/assets/default_header.png"
+
+// populateDefaultAvatars returns a slice of standard avatars found
+// in the path [web-assets-base-dir]/default_avatars. The slice
+// entries correspond to the relative url via which they can be
+// retrieved from the server.
+//
+// So for example, an avatar called default.jpeg would be returned
+// in the slice as "/assets/default_avatars/default.jpeg".
+func populateDefaultAvatars() (defaultAvatars []string) {
+ webAssetsAbsFilePath, err := filepath.Abs(config.GetWebAssetBaseDir())
+ if err != nil {
+ log.Panicf("populateDefaultAvatars: error getting abs path for web assets: %s", err)
+ }
+
+ defaultAvatarsAbsFilePath := filepath.Join(webAssetsAbsFilePath, "default_avatars")
+ defaultAvatarFiles, err := ioutil.ReadDir(defaultAvatarsAbsFilePath)
+ if err != nil {
+ log.Warnf("populateDefaultAvatars: error reading default avatars at %s: %s", defaultAvatarsAbsFilePath, err)
+ return
+ }
+
+ for _, f := range defaultAvatarFiles {
+ // ignore directories
+ if f.IsDir() {
+ continue
+ }
+
+ // ignore files bigger than 50kb
+ if f.Size() > 50000 {
+ continue
+ }
+
+ // get the name of the file, eg avatar.jpeg
+ fileName := f.Name()
+
+ // get just the .jpeg, for example, from avatar.jpeg
+ extensionWithDot := filepath.Ext(fileName)
+
+ // remove the leading . to just get, eg, jpeg
+ extension := strings.TrimPrefix(extensionWithDot, ".")
+
+ // take only files with simple extensions
+ // that we know will work OK as avatars
+ switch strings.ToLower(extension) {
+ case "jpeg", "jpg", "gif", "png":
+ avatarURL := config.GetProtocol() + "://" + config.GetHost() + "/assets/default_avatars/" + fileName
+ defaultAvatars = append(defaultAvatars, avatarURL)
+ default:
+ continue
+ }
+ }
+
+ return
+}
+
+// ensureAvatar ensures that the given account has a value set
+// for the avatar URL.
+//
+// If no value is set, an avatar will be selected at random from
+// the available default avatars. This selection is 'sticky', so
+// the same account will get the same result on subsequent calls.
+//
+// If a value for the avatar URL is already set, this function is
+// a no-op.
+//
+// If there are no default avatars available, this function is a
+// no-op.
+func (c *converter) ensureAvatar(account *apimodel.Account) {
+ if (account.Avatar != "" && account.AvatarStatic != "") || len(c.defaultAvatars) == 0 {
+ return
+ }
+
+ var avatar string
+ if avatarI, ok := c.randAvatars.Load(account.ID); ok {
+ // we already have a default avatar stored for this account
+ avatar, ok = avatarI.(string)
+ if !ok {
+ panic("avatarI was not a string")
+ }
+ } else {
+ // select + store a default avatar for this account at random
+ randomIndex := rand.Intn(len(c.defaultAvatars)) //nolint:gosec
+ avatar = c.defaultAvatars[randomIndex]
+ c.randAvatars.Store(account.ID, avatar)
+ }
+
+ account.Avatar = avatar
+ account.AvatarStatic = avatar
+}
+
+// EnsureAvatar ensures that the given account has a value set
+// for the header URL.
+//
+// If no value is set, the default header will be set.
+//
+// If a value for the header URL is already set, this function is
+// a no-op.
+func (c *converter) ensureHeader(account *apimodel.Account) {
+ if account.Header != "" && account.HeaderStatic != "" {
+ return
+ }
+
+ h := config.GetProtocol() + "://" + config.GetHost() + defaultHeaderPath
+ account.Header = h
+ account.HeaderStatic = h
+}
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index 6ccbed340..0243e4732 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -190,6 +190,9 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
Suspended: suspended,
}
+ c.ensureAvatar(accountFrontend)
+ c.ensureHeader(accountFrontend)
+
return accountFrontend, nil
}
diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go
index 648c90fba..dc92260e1 100644
--- a/internal/typeutils/internaltofrontend_test.go
+++ b/internal/typeutils/internaltofrontend_test.go
@@ -63,7 +63,7 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() {
b, err := json.Marshal(apiStatus)
suite.NoError(err)
- suite.Equal(`{"id":"01F8MH75CBF9JFX4ZAD54N0W0R","created_at":"2021-10-20T11:36:45.000Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","url":"http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","replies_count":0,"reblogs_count":0,"favourites_count":1,"favourited":true,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"hello world! #welcome ! first post on the instance :rainbow: !","reblog":null,"application":{"name":"superseriousbusiness","website":"https://superserious.business"},"account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"media_attachments":[{"id":"01F8MH6NEM8D7527KZAECTCR76","type":"image","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","text_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","preview_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpeg","remote_url":null,"preview_remote_url":null,"meta":{"original":{"width":1200,"height":630,"size":"1200x630","aspect":1.9047619},"small":{"width":256,"height":134,"size":"256x134","aspect":1.9104477},"focus":{"x":0,"y":0}},"description":"Black and white image of some 50's style text saying: Welcome On Board","blurhash":"LNJRdVM{00Rj%Mayt7j[4nWBofRj"}],"mentions":[],"tags":[{"name":"welcome","url":"http://localhost:8080/tags/welcome"}],"emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"card":null,"poll":null}`, string(b))
+ suite.Equal(`{"id":"01F8MH75CBF9JFX4ZAD54N0W0R","created_at":"2021-10-20T11:36:45.000Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","url":"http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","replies_count":0,"reblogs_count":0,"favourites_count":1,"favourited":true,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"hello world! #welcome ! first post on the instance :rainbow: !","reblog":null,"application":{"name":"superseriousbusiness","website":"https://superserious.business"},"account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"media_attachments":[{"id":"01F8MH6NEM8D7527KZAECTCR76","type":"image","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","text_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","preview_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpeg","remote_url":null,"preview_remote_url":null,"meta":{"original":{"width":1200,"height":630,"size":"1200x630","aspect":1.9047619},"small":{"width":256,"height":134,"size":"256x134","aspect":1.9104477},"focus":{"x":0,"y":0}},"description":"Black and white image of some 50's style text saying: Welcome On Board","blurhash":"LNJRdVM{00Rj%Mayt7j[4nWBofRj"}],"mentions":[],"tags":[{"name":"welcome","url":"http://localhost:8080/tags/welcome"}],"emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"card":null,"poll":null}`, string(b))
}
func (suite *InternalToFrontendTestSuite) TestInstanceToFrontend() {
@@ -108,7 +108,7 @@ func (suite *InternalToFrontendTestSuite) TestInstanceToFrontendWithAdminAccount
b, err := json.Marshal(apiInstance)
suite.NoError(err)
- suite.Equal(`{"uri":"https://example.org","title":"example instance","description":"a much longer description","short_description":"a little description","email":"someone@example.org","version":"software-from-hell 0.666","registrations":false,"approval_required":false,"invites_enabled":false,"thumbnail":"","contact_account":{"id":"01FHMQX3GAABWSM0S2VZEC2SWC","username":"some_user","acct":"some_user@example.org","display_name":"some user","locked":true,"bot":false,"created_at":"2020-08-10T12:13:28.000Z","note":"i'm a real son of a gun","url":"http://example.org/@some_user","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":0,"following_count":0,"statuses_count":0,"last_status_at":"","emojis":[],"fields":[]},"max_toot_chars":0}`, string(b))
+ suite.Equal(`{"uri":"https://example.org","title":"example instance","description":"a much longer description","short_description":"a little description","email":"someone@example.org","version":"software-from-hell 0.666","registrations":false,"approval_required":false,"invites_enabled":false,"thumbnail":"","contact_account":{"id":"01FHMQX3GAABWSM0S2VZEC2SWC","username":"some_user","acct":"some_user@example.org","display_name":"some user","locked":true,"bot":false,"created_at":"2020-08-10T12:13:28.000Z","note":"i'm a real son of a gun","url":"http://example.org/@some_user","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":0,"following_count":0,"statuses_count":0,"last_status_at":"","emojis":[],"fields":[]},"max_toot_chars":0}`, string(b))
}
func TestInternalToFrontendTestSuite(t *testing.T) {