commit 26683b3d49beea9b1f0e8f78df4720285d4c0825
parent a7e9dee33d038c55090b1e657fdf1008d3c4e227
Author: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Fri, 15 Apr 2022 14:33:01 +0200
[feature] Web profile pages for accounts (#449)
* add default avatars
* allow webModule to error
* return errWithCode from account get
* add AccountGetLocalByUsername
* check nil requesting account
* add timestampShort function for just month/year
* move loading logic to New + add default avatars
* add profile page view
* update swagger docs
* add excludeReblogs to GetAccountStatuses
* ignore casing when selecting local account by username
* appropriate redirects
* css fiddling
* add 'about' heading
* adjust thread page to work with routing
* return AP representation if requested + authorized
* simplify auth check
* go fmt
* golangci-lint ignore math/rand
Diffstat:
33 files changed, 1484 insertions(+), 87 deletions(-)
diff --git a/cmd/gotosocial/action/server/server.go b/cmd/gotosocial/action/server/server.go
@@ -148,6 +148,12 @@ var Start action.GTSAction = func(ctx context.Context) error {
return fmt.Errorf("error creating oidc idp: %s", err)
}
+ // build web module
+ webModule, err := web.New(processor)
+ if err != nil {
+ return fmt.Errorf("error creating web module: %s", err)
+ }
+
// build client api modules
authModule := auth.New(dbService, oauthServer, idp)
accountModule := account.New(processor)
@@ -156,7 +162,6 @@ var Start action.GTSAction = func(ctx context.Context) error {
followRequestsModule := followrequest.New(processor)
webfingerModule := webfinger.New(processor)
nodeInfoModule := nodeinfo.New(processor)
- webBaseModule := web.New(processor)
usersModule := user.New(processor)
timelineModule := timeline.New(processor)
notificationModule := notification.New(processor)
@@ -179,8 +184,10 @@ var Start action.GTSAction = func(ctx context.Context) error {
securityModule,
authModule,
+ // now the web module
+ webModule,
+
// now everything else
- webBaseModule,
accountModule,
instanceModule,
appsModule,
diff --git a/cmd/gotosocial/action/testrig/testrig.go b/cmd/gotosocial/action/testrig/testrig.go
@@ -95,6 +95,12 @@ var Start action.GTSAction = func(ctx context.Context) error {
return fmt.Errorf("error creating oidc idp: %s", err)
}
+ // build web module
+ webModule, err := web.New(processor)
+ if err != nil {
+ return fmt.Errorf("error creating web module: %s", err)
+ }
+
// build client api modules
authModule := auth.New(dbService, oauthServer, idp)
accountModule := account.New(processor)
@@ -103,7 +109,6 @@ var Start action.GTSAction = func(ctx context.Context) error {
followRequestsModule := followrequest.New(processor)
webfingerModule := webfinger.New(processor)
nodeInfoModule := nodeinfo.New(processor)
- webBaseModule := web.New(processor)
usersModule := user.New(processor)
timelineModule := timeline.New(processor)
notificationModule := notification.New(processor)
@@ -126,8 +131,10 @@ var Start action.GTSAction = func(ctx context.Context) error {
securityModule,
authModule,
+ // now the web module
+ webModule,
+
// now everything else
- webBaseModule,
accountModule,
instanceModule,
appsModule,
diff --git a/docs/api/swagger.yaml b/docs/api/swagger.yaml
@@ -2086,6 +2086,11 @@ paths:
in: query
name: exclude_replies
type: boolean
+ - default: false
+ description: Exclude statuses that are a reblog/boost of another status.
+ in: query
+ name: exclude_reblogs
+ type: boolean
- description: |-
Return only statuses *OLDER* than the given max status ID.
The status with the specified ID will not be included in the response.
@@ -2099,7 +2104,7 @@ paths:
name: min_id
type: string
- default: false
- description: Show only pinned statuses. In other words,e xclude statuses that
+ description: Show only pinned statuses. In other words, exclude statuses that
are not pinned to the given account ID.
in: query
name: pinned_only
diff --git a/internal/api/client/account/account.go b/internal/api/client/account/account.go
@@ -34,6 +34,8 @@ const (
LimitKey = "limit"
// ExcludeRepliesKey is for specifying whether to exclude replies in a list of returned statuses by an account.
ExcludeRepliesKey = "exclude_replies"
+ // ExcludeReblogsKey is for specifying whether to exclude reblogs in a list of returned statuses by an account.
+ ExcludeReblogsKey = "exclude_reblogs"
// PinnedKey is for specifying whether to include pinned statuses in a list of returned statuses by an account.
PinnedKey = "pinned"
// MaxIDKey is for specifying the maximum ID of the status to retrieve.
diff --git a/internal/api/client/account/accountget.go b/internal/api/client/account/accountget.go
@@ -22,6 +22,7 @@ import (
"net/http"
"github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/api"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
@@ -76,9 +77,10 @@ func (m *Module) AccountGETHandler(c *gin.Context) {
return
}
- acctInfo, err := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID)
+ acctInfo, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID)
if err != nil {
- c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
+ logrus.Debug(errWithCode.Error())
+ c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return
}
diff --git a/internal/api/client/account/statuses.go b/internal/api/client/account/statuses.go
@@ -60,6 +60,12 @@ import (
// default: false
// in: query
// required: false
+// - name: exclude_reblogs
+// type: boolean
+// description: Exclude statuses that are a reblog/boost of another status.
+// default: false
+// in: query
+// required: false
// - name: max_id
// type: string
// description: |-
@@ -75,7 +81,7 @@ import (
// required: false
// - name: pinned_only
// type: boolean
-// description: Show only pinned statuses. In other words,e xclude statuses that are not pinned to the given account ID.
+// description: Show only pinned statuses. In other words, exclude statuses that are not pinned to the given account ID.
// default: false
// in: query
// required: false
@@ -149,13 +155,25 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
if excludeRepliesString != "" {
i, err := strconv.ParseBool(excludeRepliesString)
if err != nil {
- l.Debugf("error parsing replies string: %s", err)
+ l.Debugf("error parsing exclude replies string: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse exclude replies query param"})
return
}
excludeReplies = i
}
+ excludeReblogs := false
+ excludeReblogsString := c.Query(ExcludeReblogsKey)
+ if excludeReblogsString != "" {
+ i, err := strconv.ParseBool(excludeReblogsString)
+ if err != nil {
+ l.Debugf("error parsing exclude reblogs string: %s", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse exclude reblogs query param"})
+ return
+ }
+ excludeReblogs = i
+ }
+
maxID := ""
maxIDString := c.Query(MaxIDKey)
if maxIDString != "" {
@@ -204,7 +222,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
publicOnly = i
}
- statuses, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
+ statuses, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
if errWithCode != nil {
l.Debugf("error from processor account statuses get: %s", errWithCode)
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
diff --git a/internal/api/s2s/user/inboxpost_test.go b/internal/api/s2s/user/inboxpost_test.go
@@ -440,7 +440,7 @@ func (suite *InboxPostTestSuite) TestPostDelete() {
suite.ErrorIs(err, db.ErrNoEntries)
// no statuses from foss satan should be left in the database
- dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, "", "", false, false, false)
+ dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, false, "", "", false, false, false)
suite.ErrorIs(err, db.ErrNoEntries)
suite.Empty(dbStatuses)
diff --git a/internal/db/account.go b/internal/db/account.go
@@ -52,7 +52,7 @@ type Account interface {
// then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can
// be very memory intensive so you probably shouldn't do this!
// In case of no entries, a 'no entries' error will be returned
- GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, Error)
+ GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, Error)
GetAccountBlocks(ctx context.Context, accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, Error)
diff --git a/internal/db/bundb/account.go b/internal/db/bundb/account.go
@@ -199,7 +199,7 @@ func (a *accountDB) GetLocalAccountByUsername(ctx context.Context, username stri
account := new(gtsmodel.Account)
q := a.newAccountQ(account).
- Where("username = ?", username).
+ Where("LOWER(?) = LOWER(?)", bun.Ident("username"), username). // ignore casing
WhereGroup(" AND ", whereEmptyOrNull("domain"))
if err := q.Scan(ctx); err != nil {
@@ -230,7 +230,7 @@ func (a *accountDB) CountAccountStatuses(ctx context.Context, accountID string)
Count(ctx)
}
-func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, db.Error) {
+func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, db.Error) {
statuses := []*gtsmodel.Status{}
q := a.conn.
@@ -250,6 +250,10 @@ func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, li
q = q.WhereGroup(" AND ", whereEmptyOrNull("in_reply_to_id"))
}
+ if excludeReblogs {
+ q = q.WhereGroup(" AND ", whereEmptyOrNull("boost_of_id"))
+ }
+
if maxID != "" {
q = q.Where("id < ?", maxID)
}
diff --git a/internal/processing/account.go b/internal/processing/account.go
@@ -34,16 +34,20 @@ func (p *processor) AccountDeleteLocal(ctx context.Context, authed *oauth.Auth,
return p.accountProcessor.DeleteLocal(ctx, authed.Account, form)
}
-func (p *processor) AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, error) {
+func (p *processor) AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, gtserror.WithCode) {
return p.accountProcessor.Get(ctx, authed.Account, targetAccountID)
}
+func (p *processor) AccountGetLocalByUsername(ctx context.Context, authed *oauth.Auth, username string) (*apimodel.Account, gtserror.WithCode) {
+ return p.accountProcessor.GetLocalByUsername(ctx, authed.Account, username)
+}
+
func (p *processor) AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) {
return p.accountProcessor.Update(ctx, authed.Account, form)
}
-func (p *processor) AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) {
- return p.accountProcessor.StatusesGet(ctx, authed.Account, targetAccountID, limit, excludeReplies, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
+func (p *processor) AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) {
+ return p.accountProcessor.StatusesGet(ctx, authed.Account, targetAccountID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
}
func (p *processor) AccountFollowersGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) {
diff --git a/internal/processing/account/account.go b/internal/processing/account/account.go
@@ -47,12 +47,14 @@ type Processor interface {
// Unlike Delete, it will propagate the deletion out across the federating API to other instances.
DeleteLocal(ctx context.Context, account *gtsmodel.Account, form *apimodel.AccountDeleteRequest) gtserror.WithCode
// Get processes the given request for account information.
- Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, error)
+ Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, gtserror.WithCode)
+ // GetLocalByUsername processes the given request for account information targeting a local account by username.
+ GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode)
// Update processes the update of an account with the given form
Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error)
// StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
// the account given in authed.
- StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode)
+ StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode)
// FollowersGet fetches a list of the target account's followers.
FollowersGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode)
// FollowingGet fetches a list of the accounts that target account is following.
diff --git a/internal/processing/account/delete.go b/internal/processing/account/delete.go
@@ -143,7 +143,7 @@ func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origi
var maxID string
selectStatusesLoop:
for {
- statuses, err := p.db.GetAccountStatuses(ctx, account.ID, 20, false, maxID, "", false, false, false)
+ statuses, err := p.db.GetAccountStatuses(ctx, account.ID, 20, false, false, maxID, "", false, false, false)
if err != nil {
if err == db.ErrNoEntries {
// no statuses left for this instance so we're done
diff --git a/internal/processing/account/get.go b/internal/processing/account/get.go
@@ -26,23 +26,41 @@ import (
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
-func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, error) {
+func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, gtserror.WithCode) {
targetAccount, err := p.db.GetAccountByID(ctx, targetAccountID)
if err != nil {
if err == db.ErrNoEntries {
- return nil, errors.New("account not found")
+ return nil, gtserror.NewErrorNotFound(errors.New("account not found"))
}
- return nil, fmt.Errorf("db error: %s", err)
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error: %s", err))
}
+ return p.getAccountFor(ctx, requestingAccount, targetAccount)
+}
+
+func (p *processor) GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode) {
+ targetAccount, err := p.db.GetLocalAccountByUsername(ctx, username)
+ if err != nil {
+ if err == db.ErrNoEntries {
+ return nil, gtserror.NewErrorNotFound(errors.New("account not found"))
+ }
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error: %s", err))
+ }
+
+ return p.getAccountFor(ctx, requestingAccount, targetAccount)
+}
+
+func (p *processor) getAccountFor(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (*apimodel.Account, gtserror.WithCode) {
var blocked bool
+ var err error
if requestingAccount != nil {
- blocked, err = p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true)
+ blocked, err = p.db.IsBlocked(ctx, requestingAccount.ID, targetAccount.ID, true)
if err != nil {
- return nil, fmt.Errorf("error checking account block: %s", err)
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking account block: %s", err))
}
}
@@ -50,7 +68,7 @@ func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account
if blocked {
apiAccount, err = p.tc.AccountToAPIAccountBlocked(ctx, targetAccount)
if err != nil {
- return nil, fmt.Errorf("error converting account: %s", err)
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting account: %s", err))
}
return apiAccount, nil
}
@@ -59,7 +77,7 @@ func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account
if targetAccount.Domain != "" {
targetAccountURI, err := url.Parse(targetAccount.URI)
if err != nil {
- return nil, fmt.Errorf("error parsing url %s: %s", targetAccount.URI, err)
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", targetAccount.URI, err))
}
a, err := p.federator.GetRemoteAccount(ctx, requestingAccount.Username, targetAccountURI, true, false)
@@ -74,7 +92,7 @@ func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account
apiAccount, err = p.tc.AccountToAPIAccountPublic(ctx, targetAccount)
}
if err != nil {
- return nil, fmt.Errorf("error converting account: %s", err)
+ return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting account: %s", err))
}
return apiAccount, nil
}
diff --git a/internal/processing/account/getstatuses.go b/internal/processing/account/getstatuses.go
@@ -28,16 +28,18 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
-func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) {
- if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil {
- return nil, gtserror.NewErrorInternalError(err)
- } else if blocked {
- return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts"))
+func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) {
+ if requestingAccount != nil {
+ if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil {
+ return nil, gtserror.NewErrorInternalError(err)
+ } else if blocked {
+ return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts"))
+ }
}
apiStatuses := []apimodel.Status{}
- statuses, err := p.db.GetAccountStatuses(ctx, targetAccountID, limit, excludeReplies, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
+ statuses, err := p.db.GetAccountStatuses(ctx, targetAccountID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
if err != nil {
if err == db.ErrNoEntries {
return apiStatuses, nil
diff --git a/internal/processing/federation/getoutbox.go b/internal/processing/federation/getoutbox.go
@@ -89,7 +89,7 @@ func (p *processor) GetOutbox(ctx context.Context, requestedUsername string, pag
// scenario 2 -- get the requested page
// limit pages to 30 entries per page
- publicStatuses, err := p.db.GetAccountStatuses(ctx, requestedAccount.ID, 30, true, maxID, minID, false, false, true)
+ publicStatuses, err := p.db.GetAccountStatuses(ctx, requestedAccount.ID, 30, true, true, maxID, minID, false, false, true)
if err != nil && err != db.ErrNoEntries {
return nil, gtserror.NewErrorInternalError(err)
}
diff --git a/internal/processing/federation/getuser.go b/internal/processing/federation/getuser.go
@@ -38,17 +38,20 @@ func (p *processor) GetUser(ctx context.Context, requestedUsername string, reque
}
var requestedPerson vocab.ActivityStreamsPerson
- switch {
- case uris.IsPublicKeyPath(requestURL):
+ if uris.IsPublicKeyPath(requestURL) {
// if it's a public key path, we don't need to authenticate but we'll only serve the bare minimum user profile needed for the public key
requestedPerson, err = p.tc.AccountToASMinimal(ctx, requestedAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
- case uris.IsUserPath(requestURL):
- // if it's a user path, we want to fully authenticate the request before we serve any data, and then we can serve a more complete profile
+ } else {
+ // if it's any other path, we want to fully authenticate the request before we serve any data, and then we can serve a more complete profile
requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername)
- if err != nil || !authenticated {
+ if err != nil {
+ return nil, gtserror.NewErrorNotAuthorized(err, "not authorized")
+ }
+
+ if !authenticated {
return nil, gtserror.NewErrorNotAuthorized(errors.New("not authorized"), "not authorized")
}
@@ -73,8 +76,6 @@ func (p *processor) GetUser(ctx context.Context, requestedUsername string, reque
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
- default:
- return nil, gtserror.NewErrorBadRequest(fmt.Errorf("path was not public key path or user path"))
}
data, err := streams.Serialize(requestedPerson)
diff --git a/internal/processing/fromfederator_test.go b/internal/processing/fromfederator_test.go
@@ -354,7 +354,7 @@ func (suite *FromFederatorTestSuite) TestProcessAccountDelete() {
suite.False(zorkFollowsSatan)
// no statuses from foss satan should be left in the database
- dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, "", "", false, false, false)
+ dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, false, "", "", false, false, false)
suite.ErrorIs(err, db.ErrNoEntries)
suite.Empty(dbStatuses)
diff --git a/internal/processing/processor.go b/internal/processing/processor.go
@@ -76,12 +76,14 @@ type Processor interface {
// AccountDeleteLocal processes the delete of a LOCAL account using the given form.
AccountDeleteLocal(ctx context.Context, authed *oauth.Auth, form *apimodel.AccountDeleteRequest) gtserror.WithCode
// AccountGet processes the given request for account information.
- AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, error)
+ AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, gtserror.WithCode)
+ // AccountGet processes the given request for account information.
+ AccountGetLocalByUsername(ctx context.Context, authed *oauth.Auth, username string) (*apimodel.Account, gtserror.WithCode)
// AccountUpdate processes the update of an account with the given form
AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error)
// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
// the account given in authed.
- AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode)
+ AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode)
// AccountFollowersGet fetches a list of the target account's followers.
AccountFollowersGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode)
// AccountFollowingGet fetches a list of the accounts that target account is following.
diff --git a/internal/router/template.go b/internal/router/template.go
@@ -67,6 +67,11 @@ func timestamp(stamp string) string {
return t.Format("January 2, 2006, 15:04:05")
}
+func timestampShort(stamp string) string {
+ t, _ := time.Parse(time.RFC3339, stamp)
+ return t.Format("January, 2006")
+}
+
type iconWithLabel struct {
faIcon string
label string
@@ -98,5 +103,6 @@ func LoadTemplateFunctions(engine *gin.Engine) {
"oddOrEven": oddOrEven,
"visibilityIcon": visibilityIcon,
"timestamp": timestamp,
+ "timestampShort": timestampShort,
})
}
diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go
@@ -132,7 +132,7 @@ func (suite *InternalToASTestSuite) TestStatusesToASOutboxPage() {
ctx := context.Background()
// get public statuses from testaccount
- statuses, err := suite.db.GetAccountStatuses(ctx, testAccount.ID, 30, true, "", "", false, false, true)
+ statuses, err := suite.db.GetAccountStatuses(ctx, testAccount.ID, 30, true, true, "", "", false, false, true)
suite.NoError(err)
page, err := suite.typeconverter.StatusesToASOutboxPage(ctx, testAccount.OutboxURI, "", "", statuses)
diff --git a/internal/web/base.go b/internal/web/base.go
@@ -20,8 +20,10 @@ package web
import (
"fmt"
+ "io/ioutil"
"net/http"
"path/filepath"
+ "strings"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
@@ -36,18 +38,68 @@ import (
const (
confirmEmailPath = "/" + uris.ConfirmEmailPath
tokenParam = "token"
+ usernameKey = "username"
+ statusIDKey = "status"
+ profilePath = "/@:" + usernameKey
+ statusPath = profilePath + "/statuses/:" + statusIDKey
)
// Module implements the api.ClientModule interface for web pages.
type Module struct {
- processor processing.Processor
+ processor processing.Processor
+ assetsPath string
+ adminPath string
+ defaultAvatars []string
}
// New returns a new api.ClientModule for web pages.
-func New(processor processing.Processor) api.ClientModule {
- return &Module{
- processor: processor,
+func New(processor processing.Processor) (api.ClientModule, error) {
+ assetsBaseDir := viper.GetString(config.Keys.WebAssetBaseDir)
+ if assetsBaseDir == "" {
+ return nil, fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.Keys.WebAssetBaseDir)
+ }
+
+ assetsPath, err := filepath.Abs(assetsBaseDir)
+ if err != nil {
+ return nil, fmt.Errorf("error getting absolute path of %s: %s", assetsBaseDir, err)
}
+
+ defaultAvatarsPath := filepath.Join(assetsPath, "default_avatars")
+ defaultAvatarFiles, err := ioutil.ReadDir(defaultAvatarsPath)
+ if err != nil {
+ return nil, fmt.Errorf("error reading default avatars at %s: %s", defaultAvatarsPath, err)
+ }
+
+ defaultAvatars := []string{}
+ for _, f := range defaultAvatarFiles {
+ // ignore directories
+ if f.IsDir() {
+ continue
+ }
+
+ // ignore files bigger than 50kb
+ if f.Size() > 50000 {
+ continue
+ }
+
+ extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(f.Name())), ".")
+
+ // take only files with simple extensions
+ switch extension {
+ case "svg", "jpeg", "jpg", "gif", "png":
+ defaultAvatarPath := fmt.Sprintf("/assets/default_avatars/%s", f.Name())
+ defaultAvatars = append(defaultAvatars, defaultAvatarPath)
+ default:
+ continue
+ }
+ }
+
+ return &Module{
+ processor: processor,
+ assetsPath: assetsPath,
+ adminPath: filepath.Join(assetsPath, "admin"),
+ defaultAvatars: defaultAvatars,
+ }, nil
}
func (m *Module) baseHandler(c *gin.Context) {
@@ -88,20 +140,11 @@ func (m *Module) NotFoundHandler(c *gin.Context) {
// Route satisfies the RESTAPIModule interface
func (m *Module) Route(s router.Router) error {
// serve static files from assets dir at /assets
- assetBaseDir := viper.GetString(config.Keys.WebAssetBaseDir)
- if assetBaseDir == "" {
- return fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.Keys.WebAssetBaseDir)
- }
- assetPath, err := filepath.Abs(assetBaseDir)
- if err != nil {
- return fmt.Errorf("error getting absolute path of %s: %s", assetBaseDir, err)
- }
- s.AttachStaticFS("/assets", fileSystem{http.Dir(assetPath)})
+ s.AttachStaticFS("/assets", fileSystem{http.Dir(m.assetsPath)})
// serve admin panel from within assets dir at /admin/
// and redirect /admin to /admin/
- adminPath := filepath.Join(assetPath, "admin")
- s.AttachStaticFS("/admin/", fileSystem{http.Dir(adminPath)})
+ s.AttachStaticFS("/admin/", fileSystem{http.Dir(m.adminPath)})
s.AttachHandler(http.MethodGet, "/admin", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/admin/")
})
@@ -109,8 +152,11 @@ func (m *Module) Route(s router.Router) error {
// serve front-page
s.AttachHandler(http.MethodGet, "/", m.baseHandler)
+ // serve profile pages at /@username
+ s.AttachHandler(http.MethodGet, profilePath, m.profileTemplateHandler)
+
// serve statuses
- s.AttachHandler(http.MethodGet, "/:user/statuses/:id", m.threadTemplateHandler)
+ s.AttachHandler(http.MethodGet, statusPath, m.threadTemplateHandler)
// serve email confirmation page at /confirm_email?token=whatever
s.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler)
diff --git a/internal/web/profile.go b/internal/web/profile.go
@@ -0,0 +1,139 @@
+/*
+ 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 web
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "math/rand"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/viper"
+ "github.com/superseriousbusiness/gotosocial/internal/ap"
+ "github.com/superseriousbusiness/gotosocial/internal/api"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+func (m *Module) profileTemplateHandler(c *gin.Context) {
+ l := logrus.WithField("func", "profileTemplateHandler")
+ l.Trace("rendering profile template")
+ ctx := c.Request.Context()
+
+ username := c.Param(usernameKey)
+ if username == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "no account username specified"})
+ return
+ }
+
+ authed, err := oauth.Authed(c, false, false, false, false)
+ if err != nil {
+ l.Errorf("error authing profile GET request: %s", err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
+ return
+ }
+
+ instance, errWithCode := m.processor.InstanceGet(ctx, viper.GetString(config.Keys.Host))
+ if errWithCode != nil {
+ l.Debugf("error getting instance from processor: %s", errWithCode.Error())
+ c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
+ return
+ }
+
+ account, errWithCode := m.processor.AccountGetLocalByUsername(ctx, authed, username)
+ if errWithCode != nil {
+ l.Debugf("error getting account from processor: %s", errWithCode.Error())
+ if errWithCode.Code() == http.StatusNotFound {
+ m.NotFoundHandler(c)
+ return
+ }
+ c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
+ return
+ }
+
+ // if we're getting an AP request on this endpoint we should render the account's AP representation instead
+ accept := c.NegotiateFormat(string(api.TextHTML), string(api.AppActivityJSON), string(api.AppActivityLDJSON))
+ if accept == string(api.AppActivityJSON) || accept == string(api.AppActivityLDJSON) {
+ m.returnAPRepresentation(ctx, c, username, accept)
+ return
+ }
+
+ // get latest 10 top-level public statuses;
+ // ie., exclude replies and boosts, public only,
+ // with or without media
+ statuses, errWithCode := m.processor.AccountStatusesGet(ctx, authed, account.ID, 10, true, true, "", "", false, false, true)
+ if errWithCode != nil {
+ l.Debugf("error getting statuses from processor: %s", errWithCode.Error())
+ c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
+ return
+ }
+
+ // pick a random dummy avatar if this account avatar isn't set yet
+ if account.Avatar == "" && len(m.defaultAvatars) > 0 {
+ //nolint:gosec
+ randomIndex := rand.Intn(len(m.defaultAvatars))
+ dummyAvatar := m.defaultAvatars[randomIndex]
+ account.Avatar = dummyAvatar
+ for _, s := range statuses {
+ s.Account.Avatar = dummyAvatar
+ }
+ }
+
+ c.HTML(http.StatusOK, "profile.tmpl", gin.H{
+ "instance": instance,
+ "account": account,
+ "statuses": statuses,
+ "stylesheets": []string{
+ "/assets/Fork-Awesome/css/fork-awesome.min.css",
+ "/assets/status.css",
+ "/assets/profile.css",
+ },
+ })
+}
+
+func (m *Module) returnAPRepresentation(ctx context.Context, c *gin.Context, username string, accept string) {
+ verifier, signed := c.Get(string(ap.ContextRequestingPublicKeyVerifier))
+ if signed {
+ ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeyVerifier, verifier)
+ }
+
+ signature, signed := c.Get(string(ap.ContextRequestingPublicKeySignature))
+ if signed {
+ ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeySignature, signature)
+ }
+
+ user, errWithCode := m.processor.GetFediUser(ctx, username, c.Request.URL) // GetFediUser handles auth as well
+ if errWithCode != nil {
+ logrus.Infof(errWithCode.Error())
+ c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
+ return
+ }
+
+ b, mErr := json.Marshal(user)
+ if mErr != nil {
+ err := fmt.Errorf("could not marshal json: %s", mErr)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+
+ c.Data(http.StatusOK, accept, b)
+}
diff --git a/internal/web/thread.go b/internal/web/thread.go
@@ -20,6 +20,7 @@ package web
import (
"net/http"
+ "strings"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
@@ -29,21 +30,21 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
-type statusLink struct {
- User string `uri:"user" binding:"required"`
- ID string `uri:"id" binding:"required"`
-}
-
func (m *Module) threadTemplateHandler(c *gin.Context) {
l := logrus.WithField("func", "threadTemplateGET")
l.Trace("rendering thread template")
ctx := c.Request.Context()
- var uriParts statusLink
+ username := c.Param(usernameKey)
+ if username == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "no account username specified"})
+ return
+ }
- if err := c.ShouldBindUri(&uriParts); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "status not found"})
+ statusID := c.Param(statusIDKey)
+ if username == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "no status id specified"})
return
}
@@ -62,18 +63,18 @@ func (m *Module) threadTemplateHandler(c *gin.Context) {
return
}
- status, err := m.processor.StatusGet(ctx, authed, uriParts.ID)
+ status, err := m.processor.StatusGet(ctx, authed, statusID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "status not found"})
return
}
- if uriParts.User[:1] != "@" || uriParts.User[1:] != status.Account.Username {
+ if !strings.EqualFold(username, status.Account.Username) {
c.JSON(http.StatusBadRequest, gin.H{"error": "status not found"})
return
}
- context, err := m.processor.StatusGetContext(ctx, authed, uriParts.ID)
+ context, err := m.processor.StatusGetContext(ctx, authed, statusID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "status not found"})
return
diff --git a/web/assets/base.css b/web/assets/base.css
@@ -166,23 +166,24 @@ section.login form button {
}
section.error {
- display: flex;
- flex-direction: row;
- align-items: center;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
}
+
section.error span {
- font-size: 2em;
-}
-section.error pre {
- border: 1px solid #ff000080;
- margin-left: 1em;
- padding: 0 0.7em;
- border-radius: 0.5em;
- background-color: #ff000010;
- font-size: 1.3em;
- white-space: pre-wrap;
-}
+ font-size: 2em;
+ }
+section.error pre {
+ border: 1px solid #ff000080;
+ margin-left: 1em;
+ padding: 0 0.7em;
+ border-radius: 0.5em;
+ background-color: #ff000010;
+ font-size: 1.3em;
+ white-space: pre-wrap;
+ }
input, select, textarea {
border: 1px solid #fafaff;
diff --git a/web/assets/default_avatars/GoToSocial_icon1.svg b/web/assets/default_avatars/GoToSocial_icon1.svg
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1000px"
+ height="1000px"
+ viewBox="0 0 1000 1000"
+ version="1.1"
+ id="SVGRoot"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+ sodipodi:docname="GoToSocial_icon1.svg"
+ inkscape:export-xdpi="95.999992"
+ inkscape:export-ydpi="95.999992">
+ <defs
+ id="defs5117">
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5760"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5756"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5752"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5748"
+ is_visible="true" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.70710678"
+ inkscape:cx="460.72691"
+ inkscape:cy="522.20279"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1057"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5120">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="background"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline">
+ <rect
+ style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ id="rect5876"
+ width="1000.0042"
+ height="1000"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="sloth"
+ style="display:inline">
+ <g
+ id="g5890"
+ transform="translate(-10)">
+ <path
+ sodipodi:nodetypes="ssscccs"
+ inkscape:connector-curvature="0"
+ id="path5762"
+ d="M 861.29285,497.07031 C 861.65556,665.3247 774.21642,807.40548 511.60027,807.86794 270.63622,808.29226 154.54309,691.2756 155.19024,504.19228 155.7289,348.47535 251.17288,227.4551 422.3176,205.3802 c -35.32036,-75.85452 52.24232,-96.94648 73.77615,-32.00508 13.73451,-37.63439 108.24345,-49.1716 62.21106,24.77055 147.95052,3.75658 302.58353,111.28061 302.98804,298.92464 z"
+ style="display:inline;fill:#767676;fill-opacity:1;stroke:none;stroke-width:2.57058167;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssssss"
+ inkscape:connector-curvature="0"
+ id="path5780"
+ d="m 809.15213,517.31679 c -4.83374,150.52526 -109.85544,235.22815 -297.81171,235.31839 -179.6675,0.0863 -290.56109,-70.98245 -298.50223,-235.31839 -4.6366,-95.95095 54.62861,-181.84442 144.83016,-194.18834 80.92123,-11.07393 99.7402,21.01802 153.67207,21.01802 59.21658,0 83.64871,-35.09608 162.84221,-21.85479 87.78391,14.67763 137.90533,103.6017 134.9695,195.02511 z"
+ style="display:inline;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="scssscs"
+ inkscape:connector-curvature="0"
+ id="path5780-9"
+ d="m 809.15213,517.31679 c -1.32872,41.37724 -10.22787,77.78081 -26.33906,108.8204 -46.60931,-39.48031 -99.53509,-10.7281 -171.50115,-39.43334 -44.77145,-17.85808 -51.41659,-56.56453 -51.21999,-81.3542 0.54836,-69.14384 48.17003,-93.45758 95.53601,-97.60875 55.74677,-4.88566 124.5246,36.1482 151.01547,66.79433 2.11531,14.01083 2.97167,28.36512 2.50872,42.78156 z"
+ style="display:inline;fill:#a1a1a1;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <ellipse
+ ry="50.575684"
+ rx="37.800804"
+ cy="502.64291"
+ cx="646.85773"
+ id="path5816"
+ style="fill:#767676;fill-opacity:1;stroke:none;stroke-width:1.51185882;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="scssscs"
+ inkscape:connector-curvature="0"
+ id="path5780-9-1"
+ d="m 212.51463,517.3246 c 1.32872,41.37724 10.22787,77.78081 26.33906,108.8204 46.60931,-39.48031 99.57415,-10.73591 171.54021,-39.44115 44.77145,-17.85808 51.41659,-56.56453 51.21999,-81.3542 -0.54836,-69.14384 -48.20909,-93.44977 -95.57507,-97.60094 -55.74677,-4.88566 -124.5246,36.1482 -151.01547,66.79433 -2.11531,14.01083 -2.97167,28.36512 -2.50872,42.78156 z"
+ style="display:inline;fill:#a1a1a1;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <ellipse
+ transform="scale(-1,1)"
+ ry="50.575684"
+ rx="37.800804"
+ cy="502.64294"
+ cx="-374.84808"
+ id="path5816-0"
+ style="display:inline;fill:#767676;fill-opacity:1;stroke:none;stroke-width:1.51185882;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssss"
+ inkscape:connector-curvature="0"
+ id="path5862"
+ d="m 543.96613,556.96185 c 0,11.0622 -14.51648,20.02988 -32.42347,20.02988 -17.90698,0 -32.42347,-8.96769 -32.42347,-20.02988 0,-11.0622 14.14619,-15.58638 32.05318,-15.58638 17.90698,0 32.79376,4.52417 32.79376,15.58638 z"
+ style="display:inline;fill:#767676;fill-opacity:1;stroke:none;stroke-width:1.60515046;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssss"
+ inkscape:connector-curvature="0"
+ id="path5865"
+ d="m 552.00195,620.36132 c 7.06643,13.89391 -19.38375,21.24024 -40.2832,21.24024 -20.89945,0 -47.71708,-7.02219 -41.50391,-21.24024 5.71775,-13.08435 20.11619,0.73243 41.01563,0.73243 20.89944,0 34.43888,-13.1835 40.77148,-0.73243 z"
+ style="display:inline;fill:#767676;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <ellipse
+ transform="rotate(-6.669407)"
+ ry="24.882849"
+ rx="19.511755"
+ cy="560.95673"
+ cx="600.24731"
+ id="path5818"
+ style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.53898752;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <ellipse
+ transform="rotate(-6.6694071)"
+ ry="24.882849"
+ rx="19.511755"
+ cy="529.32086"
+ cx="329.69714"
+ id="path5818-8"
+ style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.53898752;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ </g>
+ </g>
+</svg>
diff --git a/web/assets/default_avatars/GoToSocial_icon2.svg b/web/assets/default_avatars/GoToSocial_icon2.svg
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1000px"
+ height="1000px"
+ viewBox="0 0 1000 1000"
+ version="1.1"
+ id="SVGRoot"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+ sodipodi:docname="GoToSocial_icon2.svg"
+ inkscape:export-xdpi="95.999992"
+ inkscape:export-ydpi="95.999992">
+ <defs
+ id="defs5117">
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5760"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5756"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5752"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5748"
+ is_visible="true" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.35355339"
+ inkscape:cx="497.76221"
+ inkscape:cy="575.69254"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1057"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5120">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="background"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline">
+ <rect
+ style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ id="rect5876"
+ width="1000.0042"
+ height="1000"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="sloth"
+ style="display:inline">
+ <g
+ id="g3978">
+ <path
+ sodipodi:nodetypes="sccccccccccscssscsccs"
+ inkscape:connector-curvature="0"
+ id="rect5876-5"
+ d="M 230.01724,321.98276 C 349.98851,210.42521 448.21823,215.25781 531.04596,212.10691 c -43.53882,-55.72028 69.90321,-110.15948 114.22539,-8.84147 88.6448,-15.41997 77.60551,48.58211 55.52314,59.56776 116.1765,70.90805 150.01289,158.27624 164.97213,224.4903 78.29027,48.93425 33.19569,98.38368 21.79073,93.92084 57.78384,113.21004 8.74508,163.48051 -19.17636,145.16042 C 847.88417,891.22533 777.25,1000 777.25,1000 H 0 V 335 c -0.79197362,-2.0633 86.752294,193.09344 193.48008,463.67555 8.22828,-21.75326 15.34189,-32.85227 29.8481,-31.82047 12.36034,0.87917 27.76558,15.1443 13.3047,74.3212 0.95528,-0.34561 22.71708,-7.34064 29.09712,1.6646 12.30907,17.37387 -7.57175,23.95646 -1.82859,33.94605 6.04771,10.51933 23.2419,-4.11346 24.38025,-22.42448 0.95349,-15.33743 -7.68068,-33.34588 -31.29939,-31.7437 8.19555,-53.54698 -12.66475,-64.63281 -12.97883,-65.12097 -4.1124,-6.39167 -26.81347,-22.42708 -49.42662,1.72757 -15.70939,-47.21646 -51.98321,-128.67679 -77.05729,-192.68773 31.46671,-57.43102 24.70494,-162.9191 112.49771,-244.55486 z"
+ style="display:inline;fill:#777777;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssssss"
+ inkscape:connector-curvature="0"
+ id="path5899"
+ d="m 804.83231,554.45837 c 0,140.99987 -133.69246,255.30285 -298.61048,255.30285 -164.91801,0 -298.61047,-114.30298 -298.61047,-255.30285 0,-100.64813 59.92313,-198.68967 160.73026,-206.70182 60.18634,-4.78361 90.68629,22.30425 137.88021,22.81676 46.04207,0.5 70.12813,-25.6781 135.69855,-22.78348 115.9463,5.11848 162.91193,120.63643 162.91193,206.66854 z"
+ style="display:inline;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ style="display:inline;fill:#a1a1a1;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ d="m 804.83231,554.45837 c 0,13.4968 -1.22499,26.74899 -3.58509,39.67972 -71.95206,24.77201 -204.63976,33.96213 -235.31322,-11.60451 -31.67182,-47.04972 13.47247,-156.40488 88.33815,-154.51226 68.45478,1.73055 101.55237,13.1539 132.47817,31.57199 12.36622,31.25081 18.08199,64.85216 18.08199,94.86506 z"
+ id="path5937"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="scsscs" />
+ <path
+ sodipodi:nodetypes="scsscs"
+ inkscape:connector-curvature="0"
+ id="path5953"
+ d="m 207.65971,554.45837 c 0,13.4968 1.22499,26.74899 3.58509,39.67972 71.95206,24.77201 204.63976,33.96213 235.31322,-11.60451 31.67182,-47.04972 -13.47247,-156.40488 -88.33815,-154.51226 -68.45478,1.73055 -101.55237,13.1539 -132.47817,31.57199 -12.36622,31.25081 -18.08199,64.85216 -18.08199,94.86506 z"
+ style="display:inline;fill:#a1a1a1;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssss"
+ inkscape:connector-curvature="0"
+ id="path5955"
+ d="m 540.5,587.08433 c 0,11.71361 -14.7746,21.20938 -33,21.20938 -18.2254,0 -33,-9.49577 -33,-21.20938 0,-11.71362 14.0675,-13.51846 32.29289,-13.51846 18.2254,0 33.70711,1.80484 33.70711,13.51846 z"
+ style="fill:#777777;fill-opacity:1;stroke:none;stroke-width:0.34833181;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <ellipse
+ ry="44.492474"
+ rx="35.992474"
+ cy="533"
+ cx="641.5"
+ id="path5939"
+ style="display:inline;fill:#777777;fill-opacity:1;stroke:none;stroke-width:0.34905314;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <ellipse
+ transform="scale(-1,1)"
+ style="display:inline;fill:#777777;fill-opacity:1;stroke:none;stroke-width:0.34905314;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ id="ellipse5951"
+ cx="-370.992"
+ cy="533"
+ rx="35.992474"
+ ry="44.492474" />
+ </g>
+ </g>
+</svg>
diff --git a/web/assets/default_avatars/GoToSocial_icon3.svg b/web/assets/default_avatars/GoToSocial_icon3.svg
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1000px"
+ height="1000px"
+ viewBox="0 0 1000 1000"
+ version="1.1"
+ id="SVGRoot"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+ sodipodi:docname="GoToSocial_icon3.svg"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96">
+ <defs
+ id="defs5117">
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5760"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5756"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5752"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5748"
+ is_visible="true" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.5"
+ inkscape:cx="332.72586"
+ inkscape:cy="487.44283"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1057"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5120">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="background"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline">
+ <rect
+ style="fill:#dbe1ed;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ id="rect5876"
+ width="1000.0042"
+ height="1000"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="sloth"
+ style="display:inline">
+ <g
+ id="g3968">
+ <path
+ sodipodi:nodetypes="sccccccccccscssscsccs"
+ inkscape:connector-curvature="0"
+ id="rect5876-5"
+ d="M 230.01724,321.98276 C 349.98851,210.42521 448.21823,215.25781 531.04596,212.10691 c -43.53882,-55.72028 69.90321,-110.15948 114.22539,-8.84147 88.6448,-15.41997 77.60551,48.58211 55.52314,59.56776 116.1765,70.90805 150.01289,158.27624 164.97213,224.4903 78.29027,48.93425 33.19569,98.38368 21.79073,93.92084 57.78384,113.21004 8.74508,163.48051 -19.17636,145.16042 C 847.88417,891.22533 777.25,1000 777.25,1000 H 0 V 335 c -0.79197362,-2.0633 86.752294,193.09344 193.48008,463.67555 8.22828,-21.75326 15.34189,-32.85227 29.8481,-31.82047 12.36034,0.87917 27.76558,15.1443 13.3047,74.3212 0.95528,-0.34561 22.71708,-7.34064 29.09712,1.6646 12.30907,17.37387 -7.57175,23.95646 -1.82859,33.94605 6.04771,10.51933 23.2419,-4.11346 24.38025,-22.42448 0.95349,-15.33743 -7.68068,-33.34588 -31.29939,-31.7437 8.19555,-53.54698 -12.66475,-64.63281 -12.97883,-65.12097 -4.1124,-6.39167 -26.81347,-22.42708 -49.42662,1.72757 -15.70939,-47.21646 -51.98321,-128.67679 -77.05729,-192.68773 31.46671,-57.43102 24.70494,-162.9191 112.49771,-244.55486 z"
+ style="display:inline;fill:#8a9bab;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssssss"
+ inkscape:connector-curvature="0"
+ id="path5899"
+ d="m 804.83231,554.45837 c 0,140.99987 -133.69246,255.30285 -298.61048,255.30285 -164.91801,0 -298.61047,-114.30298 -298.61047,-255.30285 0,-100.64813 59.92313,-198.68967 160.73026,-206.70182 60.18634,-4.78361 90.68629,22.30425 137.88021,22.81676 46.04207,0.5 70.12813,-25.6781 135.69855,-22.78348 115.9463,5.11848 162.91193,120.63643 162.91193,206.66854 z"
+ style="display:inline;fill:#ecf1f5;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ style="display:inline;fill:#b5becf;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ d="m 804.83231,554.45837 c 0,13.4968 -1.22499,26.74899 -3.58509,39.67972 -71.95206,24.77201 -204.63976,33.96213 -235.31322,-11.60451 -31.67182,-47.04972 13.47247,-156.40488 88.33815,-154.51226 68.45478,1.73055 101.55237,13.1539 132.47817,31.57199 12.36622,31.25081 18.08199,64.85216 18.08199,94.86506 z"
+ id="path5937"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="scsscs" />
+ <path
+ sodipodi:nodetypes="scsscs"
+ inkscape:connector-curvature="0"
+ id="path5953"
+ d="m 207.65971,554.45837 c 0,13.4968 1.22499,26.74899 3.58509,39.67972 71.95206,24.77201 204.63976,33.96213 235.31322,-11.60451 31.67182,-47.04972 -13.47247,-156.40488 -88.33815,-154.51226 -68.45478,1.73055 -101.55237,13.1539 -132.47817,31.57199 -12.36622,31.25081 -18.08199,64.85216 -18.08199,94.86506 z"
+ style="display:inline;fill:#b5becf;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssss"
+ inkscape:connector-curvature="0"
+ id="path5955"
+ d="m 540.5,587.08433 c 0,11.71361 -14.7746,21.20938 -33,21.20938 -18.2254,0 -33,-9.49577 -33,-21.20938 0,-11.71362 14.0675,-13.51846 32.29289,-13.51846 18.2254,0 33.70711,1.80484 33.70711,13.51846 z"
+ style="fill:#7a7d82;fill-opacity:1;stroke:none;stroke-width:0.34833181;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <ellipse
+ ry="44.492474"
+ rx="35.992474"
+ cy="533"
+ cx="641.5"
+ id="path5939"
+ style="display:inline;fill:#7a7d82;fill-opacity:1;stroke:none;stroke-width:0.34905314;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <ellipse
+ transform="scale(-1,1)"
+ style="display:inline;fill:#7a7d82;fill-opacity:1;stroke:none;stroke-width:0.34905314;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ id="ellipse5951"
+ cx="-370.992"
+ cy="533"
+ rx="35.992474"
+ ry="44.492474" />
+ </g>
+ </g>
+</svg>
diff --git a/web/assets/default_avatars/GoToSocial_icon4.svg b/web/assets/default_avatars/GoToSocial_icon4.svg
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1000px"
+ height="1000px"
+ viewBox="0 0 1000 1000"
+ version="1.1"
+ id="SVGRoot"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+ sodipodi:docname="GoToSocial_icon4.svg"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96">
+ <defs
+ id="defs5117">
+ <inkscape:path-effect
+ is_visible="true"
+ id="path-effect6023"
+ effect="spiro" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect6019"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5760"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5756"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5752"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5748"
+ is_visible="true" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.5"
+ inkscape:cx="-238.86957"
+ inkscape:cy="384.20002"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1057"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5120">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="background"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline">
+ <rect
+ style="fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.95512283;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ id="rect6025"
+ width="1000"
+ height="1000"
+ x="-1000"
+ y="-1000"
+ transform="scale(-1)" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="sloth"
+ style="display:inline">
+ <g
+ id="g3958">
+ <path
+ sodipodi:nodetypes="ccssccccscccscscccccccc"
+ inkscape:connector-curvature="0"
+ id="rect5876"
+ d="m 250.42136,685.9676 c 27.70546,-52.03568 68.96026,3.65306 45.50576,51.34931 31.74726,11.3972 32.63935,48.28596 10.44891,60.73784 -5.04807,2.83267 -8.35938,4.85246 -6.24616,9.92615 6.82828,16.39412 69.24558,-37.84791 12.03356,-75.57698 17.99005,-83.53997 -44.99856,-94.84265 -61.49124,-71.10901 C 197.50575,583.4007 147.54378,487.681 131.62217,450.28384 c 0.12558,-13.14689 10.55647,-15.91833 16.57496,-17.76346 -52.047785,-29.70807 -27.82707,-79.31533 17.95281,-96.29615 46.52036,-17.25548 75.09848,-100.71517 158.45465,-139.02174 -41.91643,-28.95924 12.41179,-76.2933 69.63812,-60.09295 49.56384,-86.701637 128.76235,-51.997593 106.48829,-6.82033 160.08649,-21.81784 193.18485,37.08464 252.21617,29.88994 126.67808,-15.43944 115.11456,62.82898 97.64128,88.84198 21.70268,16.70276 30.36519,23.60378 61.89934,36.01667 40.08455,15.77862 59.1052,72.49434 0.74775,112.50038 40.62846,38.95859 78.93877,96.58176 32.79494,114.2793 51.96757,64.16898 32.13903,145.87433 -6.38744,145.34448 8.59645,23.3003 -5.32539,71.52714 -35.88758,62.06313 C 884.05327,852.44822 817.94577,1000 817.94577,1000 H 0 V 142.98659 c 0,0 89.549546,320.73624 250.42136,542.98101 z"
+ style="display:inline;fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssssss"
+ inkscape:connector-curvature="0"
+ id="path5899"
+ d="m 839.83231,465.22371 c 0,130.63606 -133.69246,236.53751 -298.61048,236.53751 -164.91801,0 -298.61047,-105.90145 -298.61047,-236.53751 0,-93.25027 59.92313,-184.08554 160.73026,-191.50878 60.18634,-4.432 90.68629,20.66484 137.88021,21.13968 46.04207,0.46325 70.12813,-23.7907 135.69855,-21.10884 115.9463,4.74226 162.91193,111.76938 162.91193,191.47794 z"
+ style="display:inline;fill:#f6e7e0;fill-opacity:0.98113209;stroke:none;stroke-width:1.57324922;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ style="display:inline;fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.57324922;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ d="m 839.83231,465.22371 c 0,19.38025 -2.94239,38.21613 -8.4926,56.24262 -85.83868,33.0678 -208.68682,26.13484 -229.51106,-25.32493 -26.07758,-64.44157 31.43036,-103.22868 75.8166,-116.93012 51.17758,-15.79784 106.51724,0.003 150.63827,15.41887 7.86068,23.77511 11.54879,48.25697 11.54879,70.59356 z"
+ id="path6003"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="scsscs" />
+ <path
+ sodipodi:nodetypes="sssssss"
+ inkscape:connector-curvature="0"
+ id="path6005"
+ d="m 636.34279,468.2557 c -0.9957,7.73624 0.22345,23.8871 13.0952,23.55507 15.73663,-0.40594 8.01751,-24.25641 28.62411,-24.0854 20.42062,0.16948 12.59412,24.3415 27.18325,24.95261 15.02273,0.62928 16.26485,-17.17918 14.71283,-25.30616 -4.39441,-23.01076 -22.45132,-32.32534 -42.07286,-32.70368 -21.39109,-0.41246 -38.86298,12.76845 -41.54253,33.58756 z"
+ style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:0.33399999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="scsscs"
+ inkscape:connector-curvature="0"
+ id="path6008"
+ d="m 242.46966,465.22371 c 0,19.38025 2.94239,38.21613 8.4926,56.24262 85.83868,33.0678 208.68682,26.13484 229.51106,-25.32493 26.07758,-64.44157 -31.43036,-103.22868 -75.8166,-116.93012 -51.17758,-15.79784 -106.20788,0.1024 -150.32891,15.51831 -8.25843,23.68672 -11.85815,48.15753 -11.85815,70.49412 z"
+ style="display:inline;fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.57324922;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:0.33399999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ d="m 445.95918,468.2557 c 0.9957,7.73624 -0.22345,23.8871 -13.0952,23.55507 -15.73663,-0.40594 -8.01751,-24.25641 -28.62411,-24.0854 -20.42062,0.16948 -12.59412,24.3415 -27.18325,24.95261 -15.02273,0.62928 -16.26485,-17.17918 -14.71283,-25.30616 4.39441,-23.01076 22.45132,-32.32534 42.07286,-32.70368 21.39109,-0.41246 38.86298,12.76845 41.54253,33.58756 z"
+ id="path6010"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="sssssss" />
+ <path
+ sodipodi:nodetypes="sssss"
+ inkscape:connector-curvature="0"
+ id="path6014"
+ d="m 572.40294,496.00963 c 0,10.44653 -14.00878,18.91511 -31.28948,18.91511 -17.2807,0 -31.28947,-8.46858 -31.28947,-18.91511 1e-5,-10.44652 13.65522,-13.96535 30.93592,-13.96535 17.28069,0 31.64302,3.51883 31.64303,13.96535 z"
+ style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:0.33399999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ transform="matrix(-1,0,0,1,1088.0012,0)"
+ sodipodi:nodetypes="sssscsssscs"
+ inkscape:original-d="m 561.38128,582.67417 c 4.16766,-0.91767 12.20351,2.22573 11.70451,-3.19194 -0.49904,-5.41767 3.22694,-10.0569 -0.43934,-12.36091 -3.71867,-2.33693 -4.66037,-0.70889 -10.62868,-3.03033 -6.7415,-2.62217 -12.89085,-5.33173 -18.0383,-9.27773 -5.14745,3.946 -11.25574,6.65556 -17.99724,9.27773 -5.96831,2.32144 -6.91001,0.6934 -10.62868,3.03033 -3.66628,2.30401 0.0597,6.94324 -0.43934,12.36091 -0.499,5.41767 7.53685,2.27427 11.70451,3.19194 4.16767,0.91767 12.6047,-2.8973 17.27236,-5.97963 4.66766,3.08233 13.32253,6.8973 17.4902,5.97963 z"
+ inkscape:path-effect="#path-effect6019"
+ inkscape:connector-curvature="0"
+ id="path6017"
+ d="m 561.38128,582.67417 c 2.08275,0.16617 4.19863,0.15523 6.23646,-0.30597 2.03783,-0.46119 4.00308,-1.39624 5.46805,-2.88597 1.62507,-1.65254 2.56385,-3.97206 2.49743,-6.2888 -0.0664,-2.31675 -1.15042,-4.59539 -2.93677,-6.07211 -1.43969,-1.19015 -3.25655,-1.83786 -5.08522,-2.21886 -1.82866,-0.38101 -3.69889,-0.51693 -5.54346,-0.81147 -6.78982,-1.08418 -13.20738,-4.38495 -18.0383,-9.27773 -4.81493,4.89182 -11.2195,8.19343 -17.99724,9.27773 -1.84447,0.29508 -3.7147,0.43095 -5.54339,0.8118 -1.82869,0.38084 -3.64567,1.02831 -5.08529,2.21853 -1.78611,1.47668 -2.86946,3.75542 -2.93562,6.07196 -0.0662,2.31655 0.87229,4.63564 2.49628,6.28895 1.46438,1.49081 3.42913,2.42785 5.46715,2.88979 2.03802,0.46195 4.15458,0.4723 6.23736,0.30215 6.13711,-0.50136 12.1389,-2.57916 17.27236,-5.97963 5.21036,3.40799 11.28399,5.48447 17.4902,5.97963 z"
+ style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/web/assets/default_avatars/GoToSocial_icon5.svg b/web/assets/default_avatars/GoToSocial_icon5.svg
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1000px"
+ height="1000px"
+ viewBox="0 0 1000 1000"
+ version="1.1"
+ id="SVGRoot"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+ sodipodi:docname="GoToSocial_icon5.svg"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96">
+ <defs
+ id="defs5117">
+ <inkscape:path-effect
+ is_visible="true"
+ id="path-effect6023"
+ effect="spiro" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect6019"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5760"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5756"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5752"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5748"
+ is_visible="true" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.5"
+ inkscape:cx="429.92366"
+ inkscape:cy="322.81254"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1057"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5120">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="background"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline">
+ <rect
+ style="fill:#dbe1ed;fill-opacity:1;stroke:none;stroke-width:1.95512283;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ id="rect6025"
+ width="1000"
+ height="1000"
+ x="0"
+ y="-1000"
+ transform="scale(1,-1)" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="sloth"
+ style="display:inline">
+ <g
+ id="g3356">
+ <path
+ sodipodi:nodetypes="cccsssssscscccccccssscc"
+ inkscape:connector-curvature="0"
+ id="rect5876"
+ d="m 874.87783,542.28384 c -26.11086,-42.88953 -41.80521,-83.16959 -54.18766,-152.89697 26.06687,-16.2006 24.83566,-45.46406 -15.39052,-66.46433 33.93989,-31.34323 19.98697,-65.52994 -14.72432,-74.60734 -17.82314,-4.66096 -47.16558,-7.88098 -57.36123,-10.85855 C 682.77372,222.7259 665.18888,198.90619 620.74191,184.15446 466.35494,132.9142 270.81297,170.61723 187.49013,275.95719 c -75.42408,95.35405 -76.23332,151.45419 -93.03896,234.27568 -3.607596,17.77894 -13.074363,43.57065 -18.984879,48.69769 -15.458276,13.40918 -34.278507,59.92672 20.246637,58.01857 -4.911621,26.63049 -16.202612,42.03875 -21.825893,56.4483 -17.846537,45.7314 11.791821,64.75835 49.726775,55.12543 -3.31228,29.06008 -3.80965,68.00974 41.7245,59.75469 -0.009,19.23781 7.68254,28.70603 16.36237,33.35477 C 190.37816,933.25823 216.37467,1000 216.37467,1000 H 1000 V 437.85011 c 0,0 -223.38954,219.6287 -348,370.63648 -12.36429,-39.94931 -41.10548,-27.13457 -47.02031,2.51737 -0.45414,2.27666 -3.24992,9.15209 -9.86438,8.83489 -5.83023,-0.2796 -6.27407,-11.31136 -2.97641,-22.14792 6.46074,-21.2309 39.80291,-48.85947 63.84617,-17.09603 45.93757,-54.017 128.43819,-141.91623 218.89276,-238.31106 z"
+ style="display:inline;fill:#8a9bab;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssssss"
+ inkscape:connector-curvature="0"
+ id="path5899"
+ d="m 164.41033,501.28694 c 0,134.96586 89.28037,248.33231 309.21473,247.20575 217.71706,-1.11521 307.80052,-112.23989 307.80052,-247.20575 0,-96.34096 -54.83816,-188.77266 -158.98644,-196.44194 -62.18116,-4.57889 -100.76307,28.42083 -149.52119,28.91141 -47.56808,0.4786 -79.52352,-31.65029 -147.26721,-28.87954 -119.78922,4.89943 -161.24041,114.05966 -161.24041,196.41007 z"
+ style="display:inline;fill:#ecf1f5;fill-opacity:1;stroke:none;stroke-width:1.62539303;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="cscsssc"
+ inkscape:connector-curvature="0"
+ id="path6029"
+ d="m 167.12764,541.06247 c -1.8472,-12.95996 -2.88472,-26.24998 -2.88472,-39.77553 0,-22.09219 2.88362,-43.97881 8.58421,-64.68492 40.44162,-29.67651 108.08468,-52.00242 167.32555,-41.22117 35.02394,6.37401 90.30164,43.89694 76.99739,113.45676 -7.83112,40.94415 -61.06671,49.31438 -94.96833,50.10049 -38.95967,0.9034 -90.29699,-2.69765 -155.0541,-17.87563 z"
+ style="display:inline;fill:#abb7c5;fill-opacity:1;stroke:none;stroke-width:1.62539303;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssssss"
+ inkscape:connector-curvature="0"
+ id="path6005"
+ d="m 384.49599,483.06729 c 1.26483,9.82737 -0.28385,30.34383 -16.63487,29.92206 -19.99028,-0.51567 -10.18467,-30.81298 -36.36126,-30.59574 -25.94038,0.21528 -15.99836,30.92106 -34.53097,31.69735 -19.08341,0.79938 -20.66128,-21.82275 -18.68974,-32.14647 5.58223,-29.23062 28.51996,-41.06296 53.44527,-41.54356 27.17314,-0.52397 49.36773,16.21978 52.77157,42.66636 z"
+ style="fill:#797c81;fill-opacity:1;stroke:none;stroke-width:0.424281;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ style="display:inline;fill:#abb7c5;fill-opacity:1;stroke:none;stroke-width:1.62539303;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ d="m 778.67367,541.05856 c 1.8472,-12.95996 2.75191,-26.24607 2.75191,-39.77162 0,-22.09219 -2.88362,-43.97881 -8.58421,-64.68492 -40.44162,-29.67651 -108.08468,-52.00242 -167.32555,-41.22117 -35.02394,6.37401 -90.30164,43.89694 -76.99739,113.45676 7.83112,40.94415 61.06671,49.31438 94.96833,50.10049 38.95967,0.9034 90.4298,-2.70156 155.18691,-17.87954 z"
+ id="path6027"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscsssc" />
+ <path
+ style="fill:#797c81;fill-opacity:1;stroke:none;stroke-width:0.424281;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ d="m 559.16559,483.06729 c -1.26484,9.82737 0.28385,30.34383 16.63486,29.92206 19.99028,-0.51567 10.18468,-30.81298 36.36127,-30.59574 25.94037,0.21528 15.99835,30.92106 34.53096,31.69735 19.08341,0.79938 20.66127,-21.82275 18.68974,-32.14647 -5.58223,-29.23062 -28.51996,-41.06296 -53.44525,-41.54356 -27.17316,-0.52397 -49.36774,16.21978 -52.77158,42.66636 z"
+ id="path6010"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="sssssss" />
+ <path
+ sodipodi:nodetypes="sssss"
+ inkscape:connector-curvature="0"
+ id="path6014"
+ d="m 439.99627,540.8714 c 0,10.79277 14.47309,19.54203 32.32654,19.54203 17.85345,0 32.32653,-8.74926 32.32653,-19.54203 -10e-6,-10.79276 -14.10781,-14.42822 -31.96127,-14.42822 -17.85343,0 -32.69179,3.63546 -32.6918,14.42822 z"
+ style="fill:#7a7d82;fill-opacity:1;stroke:none;stroke-width:0.34507009;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ transform="matrix(1.3299533,0,0,1.0420847,-252.3233,16.933424)"
+ sodipodi:nodetypes="sssscsssscs"
+ inkscape:original-d="m 561.38128,582.67417 c 4.16766,-0.91767 12.20351,2.22573 11.70451,-3.19194 -0.49904,-5.41767 3.22694,-10.0569 -0.43934,-12.36091 -3.71867,-2.33693 -4.66037,-0.70889 -10.62868,-3.03033 -6.7415,-2.62217 -12.89085,-5.33173 -18.0383,-9.27773 -5.14745,3.946 -11.25574,6.65556 -17.99724,9.27773 -5.96831,2.32144 -6.91001,0.6934 -10.62868,3.03033 -3.66628,2.30401 0.0597,6.94324 -0.43934,12.36091 -0.499,5.41767 7.53685,2.27427 11.70451,3.19194 4.16767,0.91767 12.6047,-2.8973 17.27236,-5.97963 4.66766,3.08233 13.32253,6.8973 17.4902,5.97963 z"
+ inkscape:path-effect="#path-effect6019"
+ inkscape:connector-curvature="0"
+ id="path6017"
+ d="m 561.38128,582.67417 c 2.08275,0.16617 4.19863,0.15523 6.23646,-0.30597 2.03783,-0.46119 4.00308,-1.39624 5.46805,-2.88597 1.62507,-1.65254 2.56385,-3.97206 2.49743,-6.2888 -0.0664,-2.31675 -1.15042,-4.59539 -2.93677,-6.07211 -1.43969,-1.19015 -3.25655,-1.83786 -5.08522,-2.21886 -1.82866,-0.38101 -3.69889,-0.51693 -5.54346,-0.81147 -6.78982,-1.08418 -13.20738,-4.38495 -18.0383,-9.27773 -4.81493,4.89182 -11.2195,8.19343 -17.99724,9.27773 -1.84447,0.29508 -3.7147,0.43095 -5.54339,0.8118 -1.82869,0.38084 -3.64567,1.02831 -5.08529,2.21853 -1.78611,1.47668 -2.86946,3.75542 -2.93562,6.07196 -0.0662,2.31655 0.87229,4.63564 2.49628,6.28895 1.46438,1.49081 3.42913,2.42785 5.46715,2.88979 2.03802,0.46195 4.15458,0.4723 6.23736,0.30215 6.13711,-0.50136 12.1389,-2.57916 17.27236,-5.97963 5.21036,3.40799 11.28399,5.48447 17.4902,5.97963 z"
+ style="fill:#7a7d82;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/web/assets/default_avatars/GoToSocial_icon6.svg b/web/assets/default_avatars/GoToSocial_icon6.svg
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1000px"
+ height="1000px"
+ viewBox="0 0 1000 1000"
+ version="1.1"
+ id="SVGRoot"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+ sodipodi:docname="GoToSocial_icon6.svg"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96">
+ <defs
+ id="defs5117">
+ <inkscape:path-effect
+ is_visible="true"
+ id="path-effect6113"
+ effect="spiro" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect6099"
+ is_visible="true" />
+ <inkscape:path-effect
+ is_visible="true"
+ id="path-effect6023"
+ effect="spiro" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect6019"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5760"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5756"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5752"
+ is_visible="true" />
+ <inkscape:path-effect
+ effect="spiro"
+ id="path-effect5748"
+ is_visible="true" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.35355339"
+ inkscape:cx="-343.35188"
+ inkscape:cy="451.98165"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1057"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5120">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="background"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline">
+ <rect
+ style="fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.95512283;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ id="rect6025"
+ width="1000"
+ height="1000"
+ x="0"
+ y="-1000"
+ transform="scale(1,-1)" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="sloth"
+ style="display:inline">
+ <g
+ id="g3345">
+ <path
+ sodipodi:nodetypes="sssccsccssscccs"
+ inkscape:connector-curvature="0"
+ id="path6057"
+ d="m 858.9826,509 c 0,21.30135 0.61819,51.5972 6.701,73.24153 7.01258,24.95272 16.3214,50.02921 6.75447,64.21164 -8.7497,12.97093 -21.25315,18.56008 -44.58836,14.66624 3.93369,101.42215 -77.47601,125.19748 -96.07496,88.79767 C 674.80251,788.46301 600.2861,811.26681 511.5,810.9826 c -93.0785,-0.29795 -169.41132,-24.34907 -226.58487,-64.56 -18.96636,55.61282 -89.53019,15.93142 -94.48786,-66.57373 -18.83753,13.90334 -40.21111,9.34959 -48.24161,-1.87396 -9.87886,-13.80685 -0.11866,-44.10059 6.46153,-66.65226 C 158.90467,576.16815 164.0174,526.56854 164.0174,509 c 0,-144.1474 114.89352,-263.91342 269.97979,-293.76458 -11.39947,-30.68702 33.19193,-42.65736 68.23504,-27.65736 51.70913,-51.5 126.88111,-8.44549 91.59407,29.31477 C 745.54432,249.30521 858.9826,367.38679 858.9826,509 Z"
+ style="display:inline;fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1.56877995;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="sssssss"
+ inkscape:connector-curvature="0"
+ id="path5899"
+ d="m 221.40138,525.50517 c 0,127.7494 118.50857,234.80637 292.6814,233.98797 167.18781,-0.78557 291.3428,-106.23857 291.3428,-233.98797 0,-91.18972 -51.90603,-178.67921 -150.48563,-185.93843 -58.85641,-4.33406 -95.37539,26.90121 -141.52648,27.36556 -45.02467,0.45301 -75.27149,-29.95799 -139.39301,-27.33539 -113.38424,4.63746 -152.61908,107.96103 -152.61908,185.90826 z"
+ style="display:inline;fill:#f6e7e0;fill-opacity:0.99460916;stroke:none;stroke-width:1.53848529;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ style="display:inline;fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.53848529;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ d="m 789.95211,601.79814 c 10.07014,-23.96206 15.47347,-49.64233 15.47347,-76.29297 0,-24.59922 -3.77719,-48.92918 -11.22886,-71.48757 -113.05163,-72.24038 -211.71014,-23.66044 -216.29587,55.37861 -0.92941,43.30708 14.85596,73.14738 66.59091,84.14256 40.60772,8.63033 109.03729,1.65487 145.46035,8.25937 z"
+ id="path6060"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csccsc" />
+ <path
+ sodipodi:nodetypes="sssss"
+ inkscape:connector-curvature="0"
+ id="path6014"
+ d="m 489.46176,567.69891 c 0,9.26255 12.42107,16.77133 27.74324,16.77133 15.32216,0 27.74323,-7.50878 27.74323,-16.77133 -10e-6,-9.26255 -12.10759,-12.38257 -27.42976,-12.38257 -15.32215,0 -28.0567,3.12002 -28.05671,12.38257 z"
+ style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:0.29614559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="aasaasa"
+ inkscape:connector-curvature="0"
+ id="path6062"
+ d="m 708.01455,512.02942 c 3.98124,8.47407 4.66411,23.87376 -3.82321,27.82668 -15.14244,7.05251 -13.43948,-27.87381 -42.39106,-26.726 -27.91154,1.10657 -23.83539,31.91957 -38.41145,26.49209 -8.14703,-3.03359 -9.71336,-17.14283 -6.5565,-25.2429 6.70872,-17.21364 23.33215,-30.04549 46.55894,-30.06845 21.25806,-0.021 37.1774,11.87004 44.62328,27.71858 z"
+ style="display:inline;fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1.49886632;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ sodipodi:nodetypes="csccsc"
+ inkscape:connector-curvature="0"
+ id="path6091"
+ d="m 236.44687,601.79814 c -10.07014,-23.96206 -15.47347,-49.64233 -15.47347,-76.29297 0,-24.59922 3.77719,-48.92918 11.22886,-71.48757 113.05163,-72.24038 211.71014,-23.66044 216.29587,55.37861 0.92941,43.30708 -14.85596,73.14738 -66.59091,84.14256 -40.60772,8.63033 -109.03729,1.65487 -145.46035,8.25937 z"
+ style="display:inline;fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.53848529;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
+ <path
+ style="display:inline;fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1.49886632;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ d="m 318.38443,512.02942 c -3.98124,8.47407 -4.66411,23.87376 3.82321,27.82668 15.14244,7.05251 13.43948,-27.87381 42.39106,-26.726 27.91154,1.10657 23.83539,31.91957 38.41145,26.49209 8.14703,-3.03359 9.71336,-17.14283 6.5565,-25.2429 -6.70872,-17.21364 -23.33215,-30.04549 -46.55894,-30.06845 -21.25806,-0.021 -37.1774,11.87004 -44.62328,27.71858 z"
+ id="path6093"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="aasaasa" />
+ <path
+ transform="matrix(0.96767168,0,0,1.0314138,16.012714,-19.869878)"
+ sodipodi:nodetypes="sscccsscs"
+ inkscape:original-d="m 581.94888,642.38075 c -0.11685,-5.42216 7.07007,-17.61775 2.82843,-16.79379 -4.24164,0.82396 -9.19339,2.47588 -16.26346,5.3033 -7.74,3.20278 -35.48656,-1.28199 -51.42842,-9.77919 -17.49196,7.64885 -42.65501,12.98197 -50.39501,9.77919 -7.07007,-2.82742 -12.02182,-4.47934 -16.26346,-5.3033 -4.24164,-0.82396 2.94528,11.37163 2.82843,16.79379 -0.11685,5.42215 41.7595,1.3486 63.7987,2.05671 22.03919,-0.70811 65.01164,3.36544 64.89479,-2.05671 z"
+ inkscape:path-effect="#path-effect6099"
+ inkscape:connector-curvature="0"
+ id="path6097"
+ d="m 581.94888,642.38075 c 2.38253,-2.24081 4.60569,-4.83839 5.47508,-7.99146 0.4347,-1.57654 0.5088,-3.26889 0.081,-4.84729 -0.42785,-1.57841 -1.37681,-3.0332 -2.7276,-3.95504 -1.26545,-0.8636 -2.83258,-1.23063 -4.36364,-1.17554 -1.53106,0.0551 -3.02872,0.51417 -4.40643,1.1843 -2.75543,1.34027 -5.02124,3.48424 -7.49339,5.29454 -7.89483,5.7812 -18.22631,8.11406 -27.83929,6.28614 -9.61298,-1.82792 -18.36718,-7.78995 -23.58913,-16.06533 -5.088,8.13656 -13.65079,14.01754 -23.07148,15.84563 -9.42069,1.82809 -19.56159,-0.42342 -27.32353,-6.06644 -2.47705,-1.80085 -4.7434,-3.94147 -7.49681,-5.28222 -1.3767,-0.67037 -2.8729,-1.13084 -4.40308,-1.18819 -1.53017,-0.0574 -3.09706,0.30649 -4.36357,1.16711 -1.35354,0.91976 -2.30564,2.37417 -2.73564,3.95314 -0.43,1.57896 -0.35707,3.27272 0.0776,4.8504 0.86942,3.15535 3.097,5.75356 5.48643,7.99025 8.48113,7.93899 19.90111,12.66409 31.51218,13.0384 11.61107,0.37431 23.31167,-3.60544 32.28652,-10.98169 9.10954,7.53977 21.01716,11.61649 32.83628,11.24191 11.81911,-0.37458 23.44469,-5.19714 32.05851,-13.29862 z"
+ style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/web/assets/profile.css b/web/assets/profile.css
@@ -0,0 +1,90 @@
+main {
+ background: transparent;
+}
+
+.headerimage img {
+ width: 100%;
+ height: 15em;
+ object-fit: cover;
+ border-radius: 10px;
+ }
+
+.profile {
+ position: relative;
+ background: rgb(75, 84, 93);
+ padding: 2rem;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-around;
+ gap: 0.5rem;
+ margin-bottom: 0.2rem;
+}
+
+.profile .basic {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 25em;
+ gap: 0.5rem;
+ }
+
+.profile .basic a {
+ position: relative;
+ z-index: 1;
+ color: inherit;
+ text-decoration: none;
+ }
+
+.profile .basic .avatar img {
+ height: 25em;
+ width: 25em;
+ object-fit: cover;
+ border-radius: 10px;
+ }
+
+.profile .basic .displayname {
+ font-weight: bold;
+ font-size: 1.6rem;
+ align-self: start;
+ }
+
+.profile .detailed {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 25em;
+ }
+
+.profile .detailed h2 {
+ margin-top: 0;
+ }
+
+.profile .detailed .bio {
+ margin: 0;
+ }
+
+.profile .detailed .bio a {
+ color: #de8957;
+ text-decoration: underline;
+ }
+
+.accountstats {
+ position: relative;
+ background: rgb(75, 84, 93);
+ padding: 0.5rem;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ gap: 0.5rem;
+ margin-bottom: 0.2rem;
+}
+
+.accountstats .entry {
+ background: rgb(89, 99, 110);
+ padding: 0.5rem;
+ flex-grow: 1;
+ text-align: center;
+ }
+
+footer + div {
+ /* something weird from the devstack.. */
+ display: none;
+}
diff --git a/web/gotosocial-styling/templates/profile.css b/web/gotosocial-styling/templates/profile.css
@@ -0,0 +1,94 @@
+main {
+ background: transparent;
+}
+
+.headerimage {
+ img {
+ width: 100%;
+ height: 15em;
+ object-fit: cover;
+ border-radius: 10px;
+ }
+}
+
+.profile {
+ position: relative;
+ background: color($bg lightness(-3%));
+ padding: 2rem;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-around;
+ gap: 0.5rem;
+ margin-bottom: 0.2rem;
+
+ .basic {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 25em;
+ gap: 0.5rem;
+
+ a {
+ position: relative;
+ z-index: 1;
+ color: inherit;
+ text-decoration: none;
+ }
+
+ .avatar {
+ img {
+ height: 25em;
+ width: 25em;
+ object-fit: cover;
+ border-radius: 10px;
+ }
+ }
+
+ .displayname {
+ font-weight: bold;
+ font-size: 1.6rem;
+ align-self: start;
+ }
+ }
+
+ .detailed {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 25em;
+
+ h2 {
+ margin-top: 0;
+ }
+
+ .bio {
+ margin: 0;
+
+ a {
+ color: $acc1;
+ text-decoration: underline;
+ }
+ }
+ }
+}
+
+.accountstats {
+ position: relative;
+ background: color($bg lightness(-3%));
+ padding: 0.5rem;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ gap: 0.5rem;
+ margin-bottom: 0.2rem;
+
+ .entry {
+ background: color($bg lightness(+3%));
+ padding: 0.5rem;
+ flex-grow: 1;
+ text-align: center;
+ }
+}
+
+footer + div {
+ /* something weird from the devstack.. */
+ display: none;
+}
diff --git a/web/template/profile.tmpl b/web/template/profile.tmpl
@@ -0,0 +1,47 @@
+{{ template "header.tmpl" .}}
+<main>
+ {{ if .account.Header }}<a href="{{.account.Header}}" class="headerimage"><img src="{{.account.Header}}"></a>{{ end }}
+ <div class="profile">
+ <div class="basic">
+ <a href="{{.account.URL}}" class="displayname">{{if .account.DisplayName}}{{.account.DisplayName}}{{else}}{{.account.Username}}{{end}}</a>
+ <a href="{{.account.URL}}" class="username">@{{.account.Username}}</a>
+ <a href="{{.account.Avatar}}" class="avatar"><img src="{{.account.Avatar}}"></a>
+ </div>
+ <div class="detailed">
+ <h2>About @{{.account.Username}}</h2>
+ <div class="bio">
+ {{ if .account.Note }}{{ .account.Note | noescape }}{{else}}This GoToSocial user hasn't written a bio yet!{{end}}
+ </div>
+ </div>
+ </div>
+ <div class="accountstats">
+ <div class="entry">Joined {{.account.CreatedAt | timestampShort}}</div>
+ <div class="entry">Followed by {{.account.FollowersCount}}</div>
+ <div class="entry">Following {{.account.FollowingCount}}</div>
+ <div class="entry">Posted {{.account.StatusesCount}}</div>
+ </div>
+ <h2>Recent public posts by @{{.account.Username}}</h2>
+ <div class="thread">
+ {{range .statuses}}
+ <div class="toot expanded">
+ {{ template "status.tmpl" .}}
+ </div>
+ {{end}}
+ </div>
+</main>
+<script>
+ Array.from(document.getElementsByClassName("spoiler-label")).forEach((label) => {
+ let checkbox = document.getElementById(label.htmlFor);
+ function update() {
+ if(checkbox.checked) {
+ label.innerHTML = "Show more";
+ } else {
+ label.innerHTML = "Show less";
+ }
+ }
+ update();
+
+ label.addEventListener("click", () => {setTimeout(update, 1)});
+ });
+</script>
+{{ template "footer.tmpl" .}}
+\ No newline at end of file