mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[feature] Set default header and avatar for API accounts to GtS ones (#799)
* validate web-asset-base-dir * move default icons into converter * always ensure avatar + header on api accounts * update tests * add default header * don't return error from web module creation anymore * tidy a bit * use pngs for default avatars rather than svgs
This commit is contained in:
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
138
internal/typeutils/defaulticons.go
Normal file
138
internal/typeutils/defaulticons.go
Normal file
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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) {
|
||||
|
Reference in New Issue
Block a user