mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-25 09:44:59 +01:00
[feature] Show + federate emojis in accounts (#837)
* Start adding account emoji * get emojis serialized + deserialized nicely * update tests * set / retrieve emojis on accounts * show account emojis in web view * fetch emojis from db based on ids * fix typo in test * lint * fix pg migration * update tests * update emoji checking logic * update comment * clarify comments + add some spacing * tidy up loops a lil (thanks kim)
This commit is contained in:
parent
15a67b7bef
commit
c4a08292ee
@ -41,6 +41,7 @@ type Accountable interface {
|
||||
WithFeatured
|
||||
WithManuallyApprovesFollowers
|
||||
WithEndpoints
|
||||
WithTag
|
||||
}
|
||||
|
||||
// Statusable represents the minimum activitypub interface for representing a 'status'.
|
||||
|
@ -200,7 +200,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerGet
|
||||
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerTwoFields() {
|
||||
// set up the request
|
||||
// we're updating the note of zork, and setting locked to true
|
||||
newBio := "this is my new bio read it and weep"
|
||||
newBio := "this is my new bio read it and weep :rainbow:"
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
@ -235,9 +235,19 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerTwo
|
||||
|
||||
// check the returned api model account
|
||||
// fields should be updated
|
||||
suite.Equal("<p>this is my new bio read it and weep</p>", apimodelAccount.Note)
|
||||
suite.Equal("<p>this is my new bio read it and weep :rainbow:</p>", apimodelAccount.Note)
|
||||
suite.Equal(newBio, apimodelAccount.Source.Note)
|
||||
suite.True(apimodelAccount.Locked)
|
||||
suite.NotEmpty(apimodelAccount.Emojis)
|
||||
suite.Equal(apimodelAccount.Emojis[0].Shortcode, "rainbow")
|
||||
|
||||
// check the account in the database
|
||||
dbZork, err := suite.db.GetAccountByID(context.Background(), apimodelAccount.ID)
|
||||
suite.NoError(err)
|
||||
suite.Equal(newBio, dbZork.NoteRaw)
|
||||
suite.Equal("<p>this is my new bio read it and weep :rainbow:</p>", dbZork.Note)
|
||||
suite.True(*dbZork.Locked)
|
||||
suite.NotEmpty(dbZork.EmojiIDs)
|
||||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerWithMedia() {
|
||||
|
@ -237,6 +237,8 @@ func (suite *InboxPostTestSuite) TestPostUnblock() {
|
||||
func (suite *InboxPostTestSuite) TestPostUpdate() {
|
||||
updatedAccount := *suite.testAccounts["remote_account_1"]
|
||||
updatedAccount.DisplayName = "updated display name!"
|
||||
testEmoji := testrig.NewTestEmojis()["rainbow"]
|
||||
updatedAccount.Emojis = []*gtsmodel.Emoji{testEmoji}
|
||||
|
||||
asAccount, err := suite.tc.AccountToAS(context.Background(), &updatedAccount)
|
||||
suite.NoError(err)
|
||||
@ -288,6 +290,15 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
|
||||
federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker)
|
||||
emailSender := testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker)
|
||||
if err := processor.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := processor.Stop(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
userModule := user.New(processor).(*user.Module)
|
||||
|
||||
// setup request
|
||||
@ -322,11 +333,21 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
|
||||
suite.Equal(http.StatusOK, result.StatusCode)
|
||||
|
||||
// account should be changed in the database now
|
||||
dbUpdatedAccount, err := suite.db.GetAccountByID(context.Background(), updatedAccount.ID)
|
||||
suite.NoError(err)
|
||||
var dbUpdatedAccount *gtsmodel.Account
|
||||
|
||||
if !testrig.WaitFor(func() bool {
|
||||
// displayName should be updated
|
||||
suite.Equal("updated display name!", dbUpdatedAccount.DisplayName)
|
||||
dbUpdatedAccount, _ = suite.db.GetAccountByID(context.Background(), updatedAccount.ID)
|
||||
return dbUpdatedAccount.DisplayName == "updated display name!"
|
||||
}) {
|
||||
suite.FailNow("timed out waiting for account update")
|
||||
}
|
||||
|
||||
// emojis should be updated
|
||||
suite.Contains(dbUpdatedAccount.EmojiIDs, testEmoji.ID)
|
||||
|
||||
// account should be freshly webfingered
|
||||
suite.WithinDuration(time.Now(), dbUpdatedAccount.LastWebfingeredAt, 10*time.Second)
|
||||
|
||||
// everything else should be the same as it was before
|
||||
suite.EqualValues(updatedAccount.Username, dbUpdatedAccount.Username)
|
||||
@ -350,7 +371,6 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
|
||||
suite.EqualValues(updatedAccount.Language, dbUpdatedAccount.Language)
|
||||
suite.EqualValues(updatedAccount.URI, dbUpdatedAccount.URI)
|
||||
suite.EqualValues(updatedAccount.URL, dbUpdatedAccount.URL)
|
||||
suite.EqualValues(updatedAccount.LastWebfingeredAt, dbUpdatedAccount.LastWebfingeredAt)
|
||||
suite.EqualValues(updatedAccount.InboxURI, dbUpdatedAccount.InboxURI)
|
||||
suite.EqualValues(updatedAccount.OutboxURI, dbUpdatedAccount.OutboxURI)
|
||||
suite.EqualValues(updatedAccount.FollowingURI, dbUpdatedAccount.FollowingURI)
|
||||
|
2
internal/cache/account.go
vendored
2
internal/cache/account.go
vendored
@ -116,6 +116,8 @@ func copyAccount(account *gtsmodel.Account) *gtsmodel.Account {
|
||||
HeaderMediaAttachment: nil,
|
||||
HeaderRemoteURL: account.HeaderRemoteURL,
|
||||
DisplayName: account.DisplayName,
|
||||
EmojiIDs: account.EmojiIDs,
|
||||
Emojis: nil,
|
||||
Fields: account.Fields,
|
||||
Note: account.Note,
|
||||
NoteRaw: account.NoteRaw,
|
||||
|
@ -42,6 +42,9 @@ type Account interface {
|
||||
// GetAccountByPubkeyID returns one account with the given public key URI (ID), or an error if something goes wrong.
|
||||
GetAccountByPubkeyID(ctx context.Context, id string) (*gtsmodel.Account, Error)
|
||||
|
||||
// PutAccount puts one account in the database.
|
||||
PutAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, Error)
|
||||
|
||||
// UpdateAccount updates one account by ID.
|
||||
UpdateAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, Error)
|
||||
|
||||
|
@ -45,7 +45,8 @@ func (a *accountDB) newAccountQ(account *gtsmodel.Account) *bun.SelectQuery {
|
||||
NewSelect().
|
||||
Model(account).
|
||||
Relation("AvatarMediaAttachment").
|
||||
Relation("HeaderMediaAttachment")
|
||||
Relation("HeaderMediaAttachment").
|
||||
Relation("Emojis")
|
||||
}
|
||||
|
||||
func (a *accountDB) GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, db.Error) {
|
||||
@ -138,24 +139,61 @@ func (a *accountDB) getAccount(ctx context.Context, cacheGet func() (*gtsmodel.A
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (a *accountDB) PutAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, db.Error) {
|
||||
if err := a.conn.RunInTx(ctx, func(tx bun.Tx) error {
|
||||
// create links between this account and any emojis it uses
|
||||
for _, i := range account.EmojiIDs {
|
||||
if _, err := tx.NewInsert().Model(>smodel.AccountToEmoji{
|
||||
AccountID: account.ID,
|
||||
EmojiID: i,
|
||||
}).Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// insert the account
|
||||
_, err := tx.NewInsert().Model(account).Exec(ctx)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, a.conn.ProcessError(err)
|
||||
}
|
||||
|
||||
a.cache.Put(account)
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (a *accountDB) UpdateAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, db.Error) {
|
||||
// Update the account's last-updated
|
||||
account.UpdatedAt = time.Now()
|
||||
|
||||
// Update the account model in the DB
|
||||
_, err := a.conn.
|
||||
NewUpdate().
|
||||
Model(account).
|
||||
WherePK().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
if err := a.conn.RunInTx(ctx, func(tx bun.Tx) error {
|
||||
// create links between this account and any emojis it uses
|
||||
// first clear out any old emoji links
|
||||
if _, err := tx.NewDelete().
|
||||
Model(&[]*gtsmodel.AccountToEmoji{}).
|
||||
Where("account_id = ?", account.ID).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now populate new emoji links
|
||||
for _, i := range account.EmojiIDs {
|
||||
if _, err := tx.NewInsert().Model(>smodel.AccountToEmoji{
|
||||
AccountID: account.ID,
|
||||
EmojiID: i,
|
||||
}).Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update the account
|
||||
_, err := tx.NewUpdate().Model(account).WherePK().Exec(ctx)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, a.conn.ProcessError(err)
|
||||
}
|
||||
|
||||
// Place updated account in cache
|
||||
// (this will replace existing, i.e. invalidating)
|
||||
a.cache.Put(account)
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,9 @@
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type AccountTestSuite struct {
|
||||
@ -71,17 +73,70 @@ func (suite *AccountTestSuite) TestGetAccountByUsernameDomain() {
|
||||
}
|
||||
|
||||
func (suite *AccountTestSuite) TestUpdateAccount() {
|
||||
ctx := context.Background()
|
||||
|
||||
testAccount := suite.testAccounts["local_account_1"]
|
||||
|
||||
testAccount.DisplayName = "new display name!"
|
||||
testAccount.EmojiIDs = []string{"01GD36ZKWTKY3T1JJ24JR7KY1Q", "01GD36ZV904SHBHNAYV6DX5QEF"}
|
||||
|
||||
_, err := suite.db.UpdateAccount(context.Background(), testAccount)
|
||||
_, err := suite.db.UpdateAccount(ctx, testAccount)
|
||||
suite.NoError(err)
|
||||
|
||||
updated, err := suite.db.GetAccountByID(context.Background(), testAccount.ID)
|
||||
updated, err := suite.db.GetAccountByID(ctx, testAccount.ID)
|
||||
suite.NoError(err)
|
||||
suite.Equal("new display name!", updated.DisplayName)
|
||||
suite.Equal([]string{"01GD36ZKWTKY3T1JJ24JR7KY1Q", "01GD36ZV904SHBHNAYV6DX5QEF"}, updated.EmojiIDs)
|
||||
suite.WithinDuration(time.Now(), updated.UpdatedAt, 5*time.Second)
|
||||
|
||||
// get account without cache + make sure it's really in the db as desired
|
||||
dbService, ok := suite.db.(*bundb.DBService)
|
||||
if !ok {
|
||||
panic("db was not *bundb.DBService")
|
||||
}
|
||||
|
||||
noCache := >smodel.Account{}
|
||||
err = dbService.GetConn().
|
||||
NewSelect().
|
||||
Model(noCache).
|
||||
Where("account.id = ?", bun.Ident(testAccount.ID)).
|
||||
Relation("AvatarMediaAttachment").
|
||||
Relation("HeaderMediaAttachment").
|
||||
Relation("Emojis").
|
||||
Scan(ctx)
|
||||
|
||||
suite.NoError(err)
|
||||
suite.Equal("new display name!", noCache.DisplayName)
|
||||
suite.Equal([]string{"01GD36ZKWTKY3T1JJ24JR7KY1Q", "01GD36ZV904SHBHNAYV6DX5QEF"}, noCache.EmojiIDs)
|
||||
suite.WithinDuration(time.Now(), noCache.UpdatedAt, 5*time.Second)
|
||||
suite.NotNil(noCache.AvatarMediaAttachment)
|
||||
suite.NotNil(noCache.HeaderMediaAttachment)
|
||||
|
||||
// update again to remove emoji associations
|
||||
testAccount.EmojiIDs = []string{}
|
||||
|
||||
_, err = suite.db.UpdateAccount(ctx, testAccount)
|
||||
suite.NoError(err)
|
||||
|
||||
updated, err = suite.db.GetAccountByID(ctx, testAccount.ID)
|
||||
suite.NoError(err)
|
||||
suite.Equal("new display name!", updated.DisplayName)
|
||||
suite.Empty(updated.EmojiIDs)
|
||||
suite.WithinDuration(time.Now(), updated.UpdatedAt, 5*time.Second)
|
||||
|
||||
err = dbService.GetConn().
|
||||
NewSelect().
|
||||
Model(noCache).
|
||||
Where("account.id = ?", bun.Ident(testAccount.ID)).
|
||||
Relation("AvatarMediaAttachment").
|
||||
Relation("HeaderMediaAttachment").
|
||||
Relation("Emojis").
|
||||
Scan(ctx)
|
||||
|
||||
suite.NoError(err)
|
||||
suite.Equal("new display name!", noCache.DisplayName)
|
||||
suite.Empty(noCache.EmojiIDs)
|
||||
suite.WithinDuration(time.Now(), noCache.UpdatedAt, 5*time.Second)
|
||||
}
|
||||
|
||||
func (suite *AccountTestSuite) TestInsertAccountWithDefaults() {
|
||||
|
@ -67,12 +67,13 @@
|
||||
)
|
||||
|
||||
var registerTables = []interface{}{
|
||||
>smodel.AccountToEmoji{},
|
||||
>smodel.StatusToEmoji{},
|
||||
>smodel.StatusToTag{},
|
||||
}
|
||||
|
||||
// bunDBService satisfies the DB interface
|
||||
type bunDBService struct {
|
||||
// DBService satisfies the DB interface
|
||||
type DBService struct {
|
||||
db.Account
|
||||
db.Admin
|
||||
db.Basic
|
||||
@ -89,6 +90,12 @@ type bunDBService struct {
|
||||
conn *DBConn
|
||||
}
|
||||
|
||||
// GetConn returns the underlying bun connection.
|
||||
// Should only be used in testing + exceptional circumstance.
|
||||
func (dbService *DBService) GetConn() *DBConn {
|
||||
return dbService.conn
|
||||
}
|
||||
|
||||
func doMigration(ctx context.Context, db *bun.DB) error {
|
||||
migrator := migrate.NewMigrator(db, migrations.Migrations)
|
||||
|
||||
@ -177,7 +184,7 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
|
||||
// Prepare domain block cache
|
||||
blockCache := cache.NewDomainBlockCache()
|
||||
|
||||
ps := &bunDBService{
|
||||
ps := &DBService{
|
||||
Account: accounts,
|
||||
Admin: &adminDB{
|
||||
conn: conn,
|
||||
@ -399,7 +406,7 @@ func tweakConnectionValues(sqldb *sql.DB) {
|
||||
CONVERSION FUNCTIONS
|
||||
*/
|
||||
|
||||
func (ps *bunDBService) TagStringsToTags(ctx context.Context, tags []string, originAccountID string) ([]*gtsmodel.Tag, error) {
|
||||
func (dbService *DBService) TagStringsToTags(ctx context.Context, tags []string, originAccountID string) ([]*gtsmodel.Tag, error) {
|
||||
protocol := config.GetProtocol()
|
||||
host := config.GetHost()
|
||||
|
||||
@ -408,7 +415,7 @@ func (ps *bunDBService) TagStringsToTags(ctx context.Context, tags []string, ori
|
||||
tag := >smodel.Tag{}
|
||||
// we can use selectorinsert here to create the new tag if it doesn't exist already
|
||||
// inserted will be true if this is a new tag we just created
|
||||
if err := ps.conn.NewSelect().Model(tag).Where("LOWER(?) = LOWER(?)", bun.Ident("name"), t).Scan(ctx); err != nil {
|
||||
if err := dbService.conn.NewSelect().Model(tag).Where("LOWER(?) = LOWER(?)", bun.Ident("name"), t).Scan(ctx); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
// tag doesn't exist yet so populate it
|
||||
newID, err := id.NewRandomULID()
|
||||
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
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 migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
q := tx.NewAddColumn().Model(>smodel.Account{})
|
||||
|
||||
switch tx.Dialect().Name() {
|
||||
case dialect.PG:
|
||||
q = q.ColumnExpr("? VARCHAR[]", bun.Ident("emojis"))
|
||||
case dialect.SQLite:
|
||||
q = q.ColumnExpr("? VARCHAR", bun.Ident("emojis"))
|
||||
default:
|
||||
log.Panic("db dialect was neither pg nor sqlite")
|
||||
}
|
||||
|
||||
if _, err := q.Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model(>smodel.AccountToEmoji{}).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -76,6 +76,11 @@ type GetRemoteAccountParams struct {
|
||||
// quickly fetch a remote account from the database or fail, and don't want to cause
|
||||
// http requests to go flying around.
|
||||
SkipResolve bool
|
||||
// PartialAccount can be used if the GetRemoteAccount call results from a federated/ap
|
||||
// account update. In this case, we will already have a partial representation of the account,
|
||||
// derived from converting the AP representation to a gtsmodel representation. If this field
|
||||
// is provided, then GetRemoteAccount will use this as a basis for building the full account.
|
||||
PartialAccount *gtsmodel.Account
|
||||
}
|
||||
|
||||
// GetRemoteAccount completely dereferences a remote account, converts it to a GtS model account,
|
||||
@ -107,8 +112,16 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar
|
||||
skipResolve := params.SkipResolve
|
||||
|
||||
// this first step checks if we have the
|
||||
// account in the database somewhere already
|
||||
// account in the database somewhere already,
|
||||
// or if we've been provided it as a partial
|
||||
switch {
|
||||
case params.PartialAccount != nil:
|
||||
foundAccount = params.PartialAccount
|
||||
if foundAccount.Domain == "" || foundAccount.Domain == config.GetHost() || foundAccount.Domain == config.GetAccountDomain() {
|
||||
// this is actually a local account,
|
||||
// make sure we don't try to resolve
|
||||
skipResolve = true
|
||||
}
|
||||
case params.RemoteAccountID != nil:
|
||||
uri := params.RemoteAccountID
|
||||
host := uri.Host
|
||||
@ -163,7 +176,7 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar
|
||||
params.RemoteAccountHost = params.RemoteAccountID.Host
|
||||
// ... but we still need the username so we can do a finger for the accountDomain
|
||||
|
||||
// check if we had the account stored already and got it earlier
|
||||
// check if we got the account earlier
|
||||
if foundAccount != nil {
|
||||
params.RemoteAccountUsername = foundAccount.Username
|
||||
} else {
|
||||
@ -201,9 +214,10 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar
|
||||
// to save on remote calls, only webfinger if:
|
||||
// - we don't know the remote account ActivityPub ID yet OR
|
||||
// - we haven't found the account yet in some other way OR
|
||||
// - we were passed a partial account in params OR
|
||||
// - we haven't webfingered the account for two days AND the account isn't an instance account
|
||||
var fingered time.Time
|
||||
if params.RemoteAccountID == nil || foundAccount == nil || (foundAccount.LastWebfingeredAt.Before(time.Now().Add(webfingerInterval)) && !instanceAccount(foundAccount)) {
|
||||
if params.RemoteAccountID == nil || foundAccount == nil || params.PartialAccount != nil || (foundAccount.LastWebfingeredAt.Before(time.Now().Add(webfingerInterval)) && !instanceAccount(foundAccount)) {
|
||||
accountDomain, params.RemoteAccountID, err = d.fingerRemoteAccount(ctx, params.RequestingUsername, params.RemoteAccountUsername, params.RemoteAccountHost)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("GetRemoteAccount: error while fingering: %s", err)
|
||||
@ -263,7 +277,7 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar
|
||||
foundAccount.LastWebfingeredAt = fingered
|
||||
foundAccount.UpdatedAt = time.Now()
|
||||
|
||||
err = d.db.Put(ctx, foundAccount)
|
||||
foundAccount, err = d.db.PutAccount(ctx, foundAccount)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("GetRemoteAccount: error putting new account: %s", err)
|
||||
return
|
||||
@ -273,13 +287,10 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar
|
||||
}
|
||||
|
||||
// we had the account already, but now we know the account domain, so update it if it's different
|
||||
var accountDomainChanged bool
|
||||
if !strings.EqualFold(foundAccount.Domain, accountDomain) {
|
||||
accountDomainChanged = true
|
||||
foundAccount.Domain = accountDomain
|
||||
foundAccount, err = d.db.UpdateAccount(ctx, foundAccount)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("GetRemoteAccount: error updating account: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// if SharedInboxURI is nil, that means we don't know yet if this account has
|
||||
@ -327,8 +338,7 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar
|
||||
foundAccount.LastWebfingeredAt = fingered
|
||||
}
|
||||
|
||||
if fieldsChanged || fingeredChanged || sharedInboxChanged {
|
||||
foundAccount.UpdatedAt = time.Now()
|
||||
if accountDomainChanged || sharedInboxChanged || fieldsChanged || fingeredChanged {
|
||||
foundAccount, err = d.db.UpdateAccount(ctx, foundAccount)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetRemoteAccount: error updating remoteAccount: %s", err)
|
||||
@ -423,15 +433,20 @@ func (d *deref) populateAccountFields(ctx context.Context, account *gtsmodel.Acc
|
||||
return false, fmt.Errorf("populateAccountFields: domain %s is blocked", accountURI.Host)
|
||||
}
|
||||
|
||||
t, err := d.transportController.NewTransportForUsername(ctx, requestingUsername)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("populateAccountFields: error getting transport for user: %s", err)
|
||||
}
|
||||
var changed bool
|
||||
|
||||
// fetch the header and avatar
|
||||
changed, err := d.fetchRemoteAccountMedia(ctx, account, t, blocking)
|
||||
if err != nil {
|
||||
if mediaChanged, err := d.fetchRemoteAccountMedia(ctx, account, requestingUsername, blocking); err != nil {
|
||||
return false, fmt.Errorf("populateAccountFields: error fetching header/avi for account: %s", err)
|
||||
} else if mediaChanged {
|
||||
changed = mediaChanged
|
||||
}
|
||||
|
||||
// fetch any emojis used in note, fields, display name, etc
|
||||
if emojisChanged, err := d.fetchRemoteAccountEmojis(ctx, account, requestingUsername); err != nil {
|
||||
return false, fmt.Errorf("populateAccountFields: error fetching emojis for account: %s", err)
|
||||
} else if emojisChanged {
|
||||
changed = emojisChanged
|
||||
}
|
||||
|
||||
return changed, nil
|
||||
@ -449,17 +464,11 @@ func (d *deref) populateAccountFields(ctx context.Context, account *gtsmodel.Acc
|
||||
//
|
||||
// If blocking is true, then the calls to the media manager made by this function will be blocking:
|
||||
// in other words, the function won't return until the header and the avatar have been fully processed.
|
||||
func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsmodel.Account, t transport.Transport, blocking bool) (bool, error) {
|
||||
changed := false
|
||||
|
||||
accountURI, err := url.Parse(targetAccount.URI)
|
||||
if err != nil {
|
||||
return changed, fmt.Errorf("fetchRemoteAccountMedia: couldn't parse account URI %s: %s", targetAccount.URI, err)
|
||||
}
|
||||
|
||||
if blocked, err := d.db.IsDomainBlocked(ctx, accountURI.Host); blocked || err != nil {
|
||||
return changed, fmt.Errorf("fetchRemoteAccountMedia: domain %s is blocked", accountURI.Host)
|
||||
}
|
||||
func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsmodel.Account, requestingUsername string, blocking bool) (bool, error) {
|
||||
var (
|
||||
changed bool
|
||||
t transport.Transport
|
||||
)
|
||||
|
||||
if targetAccount.AvatarRemoteURL != "" && (targetAccount.AvatarMediaAttachmentID == "") {
|
||||
var processingMedia *media.ProcessingMedia
|
||||
@ -479,6 +488,14 @@ func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsm
|
||||
return changed, err
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
var err error
|
||||
t, err = d.transportController.NewTransportForUsername(ctx, requestingUsername)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("fetchRemoteAccountMedia: error getting transport for user: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
data := func(innerCtx context.Context) (io.Reader, int, error) {
|
||||
return t.DereferenceMedia(innerCtx, avatarIRI)
|
||||
}
|
||||
@ -537,6 +554,14 @@ func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsm
|
||||
return changed, err
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
var err error
|
||||
t, err = d.transportController.NewTransportForUsername(ctx, requestingUsername)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("fetchRemoteAccountMedia: error getting transport for user: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
data := func(innerCtx context.Context) (io.Reader, int, error) {
|
||||
return t.DereferenceMedia(innerCtx, headerIRI)
|
||||
}
|
||||
@ -580,6 +605,118 @@ func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsm
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
func (d *deref) fetchRemoteAccountEmojis(ctx context.Context, targetAccount *gtsmodel.Account, requestingUsername string) (bool, error) {
|
||||
maybeEmojis := targetAccount.Emojis
|
||||
maybeEmojiIDs := targetAccount.EmojiIDs
|
||||
|
||||
// It's possible that the account had emoji IDs set on it, but not Emojis
|
||||
// themselves, depending on how it was fetched before being passed to us.
|
||||
//
|
||||
// If we only have IDs, fetch the emojis from the db. We know they're in
|
||||
// there or else they wouldn't have IDs.
|
||||
if len(maybeEmojiIDs) > len(maybeEmojis) {
|
||||
maybeEmojis = []*gtsmodel.Emoji{}
|
||||
for _, emojiID := range maybeEmojiIDs {
|
||||
maybeEmoji, err := d.db.GetEmojiByID(ctx, emojiID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
maybeEmojis = append(maybeEmojis, maybeEmoji)
|
||||
}
|
||||
}
|
||||
|
||||
// For all the maybe emojis we have, we either fetch them from the database
|
||||
// (if we haven't already), or dereference them from the remote instance.
|
||||
gotEmojis, err := d.populateEmojis(ctx, maybeEmojis, requestingUsername)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Extract the ID of each fetched or dereferenced emoji, so we can attach
|
||||
// this to the account if necessary.
|
||||
gotEmojiIDs := make([]string, 0, len(gotEmojis))
|
||||
for _, e := range gotEmojis {
|
||||
gotEmojiIDs = append(gotEmojiIDs, e.ID)
|
||||
}
|
||||
|
||||
var (
|
||||
changed = false // have the emojis for this account changed?
|
||||
maybeLen = len(maybeEmojis)
|
||||
gotLen = len(gotEmojis)
|
||||
)
|
||||
|
||||
// if the length of everything is zero, this is simple:
|
||||
// nothing has changed and there's nothing to do
|
||||
if maybeLen == 0 && gotLen == 0 {
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
// if the *amount* of emojis on the account has changed, then the got emojis
|
||||
// are definitely different from the previous ones (if there were any) --
|
||||
// the account has either more or fewer emojis set on it now, so take the
|
||||
// discovered emojis as the new correct ones.
|
||||
if maybeLen != gotLen {
|
||||
changed = true
|
||||
targetAccount.Emojis = gotEmojis
|
||||
targetAccount.EmojiIDs = gotEmojiIDs
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
// if the lengths are the same but not all of the slices are
|
||||
// zero, something *might* have changed, so we have to check
|
||||
|
||||
// 1. did we have emojis before that we don't have now?
|
||||
for _, maybeEmoji := range maybeEmojis {
|
||||
var stillPresent bool
|
||||
|
||||
for _, gotEmoji := range gotEmojis {
|
||||
if maybeEmoji.URI == gotEmoji.URI {
|
||||
// the emoji we maybe had is still present now,
|
||||
// so we can stop checking gotEmojis
|
||||
stillPresent = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !stillPresent {
|
||||
// at least one maybeEmoji is no longer present in
|
||||
// the got emojis, so we can stop checking now
|
||||
changed = true
|
||||
targetAccount.Emojis = gotEmojis
|
||||
targetAccount.EmojiIDs = gotEmojiIDs
|
||||
return changed, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 2. do we have emojis now that we didn't have before?
|
||||
for _, gotEmoji := range gotEmojis {
|
||||
var wasPresent bool
|
||||
|
||||
for _, maybeEmoji := range maybeEmojis {
|
||||
// check emoji IDs here as well, because unreferenced
|
||||
// maybe emojis we didn't already have would not have
|
||||
// had IDs set on them yet
|
||||
if gotEmoji.URI == maybeEmoji.URI && gotEmoji.ID == maybeEmoji.ID {
|
||||
// this got emoji was present already in the maybeEmoji,
|
||||
// so we can stop checking through maybeEmojis
|
||||
wasPresent = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !wasPresent {
|
||||
// at least one gotEmojis was not present in
|
||||
// the maybeEmojis, so we can stop checking now
|
||||
changed = true
|
||||
targetAccount.Emojis = gotEmojis
|
||||
targetAccount.EmojiIDs = gotEmojiIDs
|
||||
return changed, nil
|
||||
}
|
||||
}
|
||||
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
func lockAndLoad(ctx context.Context, lock *sync.Mutex, processing *media.ProcessingMedia, processingMap map[string]*media.ProcessingMedia, accountID string) error {
|
||||
// whatever happens, remove the in-process media from the map
|
||||
defer func() {
|
||||
|
@ -27,6 +27,7 @@
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
@ -195,6 +196,205 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountWithUnknownUserURI() {
|
||||
suite.Nil(fetchedAccount)
|
||||
}
|
||||
|
||||
func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithPartial() {
|
||||
fetchingAccount := suite.testAccounts["local_account_1"]
|
||||
|
||||
remoteAccount := suite.testAccounts["remote_account_1"]
|
||||
remoteAccountPartial := >smodel.Account{
|
||||
ID: remoteAccount.ID,
|
||||
ActorType: remoteAccount.ActorType,
|
||||
Language: remoteAccount.Language,
|
||||
CreatedAt: remoteAccount.CreatedAt,
|
||||
UpdatedAt: remoteAccount.UpdatedAt,
|
||||
Username: remoteAccount.Username,
|
||||
Domain: remoteAccount.Domain,
|
||||
DisplayName: remoteAccount.DisplayName,
|
||||
URI: remoteAccount.URI,
|
||||
InboxURI: remoteAccount.URI,
|
||||
SharedInboxURI: remoteAccount.SharedInboxURI,
|
||||
PublicKeyURI: remoteAccount.PublicKeyURI,
|
||||
URL: remoteAccount.URL,
|
||||
FollowingURI: remoteAccount.FollowingURI,
|
||||
FollowersURI: remoteAccount.FollowersURI,
|
||||
OutboxURI: remoteAccount.OutboxURI,
|
||||
FeaturedCollectionURI: remoteAccount.FeaturedCollectionURI,
|
||||
Emojis: []*gtsmodel.Emoji{
|
||||
// dereference an emoji we don't have stored yet
|
||||
{
|
||||
URI: "http://fossbros-anonymous.io/emoji/01GD5HCC2YECT012TK8PAGX4D1",
|
||||
Shortcode: "kip_van_den_bos",
|
||||
UpdatedAt: testrig.TimeMustParse("2022-09-13T12:13:12+02:00"),
|
||||
ImageRemoteURL: "http://fossbros-anonymous.io/emoji/kip.gif",
|
||||
Disabled: testrig.FalseBool(),
|
||||
VisibleInPicker: testrig.FalseBool(),
|
||||
Domain: "fossbros-anonymous.io",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetchedAccount, err := suite.dereferencer.GetRemoteAccount(context.Background(), dereferencing.GetRemoteAccountParams{
|
||||
RequestingUsername: fetchingAccount.Username,
|
||||
RemoteAccountID: testrig.URLMustParse(remoteAccount.URI),
|
||||
RemoteAccountHost: remoteAccount.Domain,
|
||||
RemoteAccountUsername: remoteAccount.Username,
|
||||
PartialAccount: remoteAccountPartial,
|
||||
Blocking: true,
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.NotNil(fetchedAccount)
|
||||
suite.NotNil(fetchedAccount.EmojiIDs)
|
||||
suite.NotNil(fetchedAccount.Emojis)
|
||||
}
|
||||
|
||||
func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithPartial2() {
|
||||
fetchingAccount := suite.testAccounts["local_account_1"]
|
||||
|
||||
knownEmoji := suite.testEmojis["yell"]
|
||||
|
||||
remoteAccount := suite.testAccounts["remote_account_1"]
|
||||
remoteAccountPartial := >smodel.Account{
|
||||
ID: remoteAccount.ID,
|
||||
ActorType: remoteAccount.ActorType,
|
||||
Language: remoteAccount.Language,
|
||||
CreatedAt: remoteAccount.CreatedAt,
|
||||
UpdatedAt: remoteAccount.UpdatedAt,
|
||||
Username: remoteAccount.Username,
|
||||
Domain: remoteAccount.Domain,
|
||||
DisplayName: remoteAccount.DisplayName,
|
||||
URI: remoteAccount.URI,
|
||||
InboxURI: remoteAccount.URI,
|
||||
SharedInboxURI: remoteAccount.SharedInboxURI,
|
||||
PublicKeyURI: remoteAccount.PublicKeyURI,
|
||||
URL: remoteAccount.URL,
|
||||
FollowingURI: remoteAccount.FollowingURI,
|
||||
FollowersURI: remoteAccount.FollowersURI,
|
||||
OutboxURI: remoteAccount.OutboxURI,
|
||||
FeaturedCollectionURI: remoteAccount.FeaturedCollectionURI,
|
||||
Emojis: []*gtsmodel.Emoji{
|
||||
// an emoji we already have
|
||||
{
|
||||
URI: knownEmoji.URI,
|
||||
Shortcode: knownEmoji.Shortcode,
|
||||
UpdatedAt: knownEmoji.CreatedAt,
|
||||
ImageRemoteURL: knownEmoji.ImageRemoteURL,
|
||||
Disabled: knownEmoji.Disabled,
|
||||
VisibleInPicker: knownEmoji.VisibleInPicker,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetchedAccount, err := suite.dereferencer.GetRemoteAccount(context.Background(), dereferencing.GetRemoteAccountParams{
|
||||
RequestingUsername: fetchingAccount.Username,
|
||||
RemoteAccountID: testrig.URLMustParse(remoteAccount.URI),
|
||||
RemoteAccountHost: remoteAccount.Domain,
|
||||
RemoteAccountUsername: remoteAccount.Username,
|
||||
PartialAccount: remoteAccountPartial,
|
||||
Blocking: true,
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.NotNil(fetchedAccount)
|
||||
suite.NotNil(fetchedAccount.EmojiIDs)
|
||||
suite.NotNil(fetchedAccount.Emojis)
|
||||
}
|
||||
|
||||
func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithPartial3() {
|
||||
fetchingAccount := suite.testAccounts["local_account_1"]
|
||||
|
||||
knownEmoji := suite.testEmojis["yell"]
|
||||
|
||||
remoteAccount := suite.testAccounts["remote_account_1"]
|
||||
remoteAccountPartial := >smodel.Account{
|
||||
ID: remoteAccount.ID,
|
||||
ActorType: remoteAccount.ActorType,
|
||||
Language: remoteAccount.Language,
|
||||
CreatedAt: remoteAccount.CreatedAt,
|
||||
UpdatedAt: remoteAccount.UpdatedAt,
|
||||
Username: remoteAccount.Username,
|
||||
Domain: remoteAccount.Domain,
|
||||
DisplayName: remoteAccount.DisplayName,
|
||||
URI: remoteAccount.URI,
|
||||
InboxURI: remoteAccount.URI,
|
||||
SharedInboxURI: remoteAccount.SharedInboxURI,
|
||||
PublicKeyURI: remoteAccount.PublicKeyURI,
|
||||
URL: remoteAccount.URL,
|
||||
FollowingURI: remoteAccount.FollowingURI,
|
||||
FollowersURI: remoteAccount.FollowersURI,
|
||||
OutboxURI: remoteAccount.OutboxURI,
|
||||
FeaturedCollectionURI: remoteAccount.FeaturedCollectionURI,
|
||||
Emojis: []*gtsmodel.Emoji{
|
||||
// an emoji we already have
|
||||
{
|
||||
URI: knownEmoji.URI,
|
||||
Shortcode: knownEmoji.Shortcode,
|
||||
UpdatedAt: knownEmoji.CreatedAt,
|
||||
ImageRemoteURL: knownEmoji.ImageRemoteURL,
|
||||
Disabled: knownEmoji.Disabled,
|
||||
VisibleInPicker: knownEmoji.VisibleInPicker,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetchedAccount, err := suite.dereferencer.GetRemoteAccount(context.Background(), dereferencing.GetRemoteAccountParams{
|
||||
RequestingUsername: fetchingAccount.Username,
|
||||
RemoteAccountID: testrig.URLMustParse(remoteAccount.URI),
|
||||
RemoteAccountHost: remoteAccount.Domain,
|
||||
RemoteAccountUsername: remoteAccount.Username,
|
||||
PartialAccount: remoteAccountPartial,
|
||||
Blocking: true,
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.NotNil(fetchedAccount)
|
||||
suite.NotNil(fetchedAccount.EmojiIDs)
|
||||
suite.NotNil(fetchedAccount.Emojis)
|
||||
suite.Equal(knownEmoji.URI, fetchedAccount.Emojis[0].URI)
|
||||
|
||||
remoteAccountPartial2 := >smodel.Account{
|
||||
ID: remoteAccount.ID,
|
||||
ActorType: remoteAccount.ActorType,
|
||||
Language: remoteAccount.Language,
|
||||
CreatedAt: remoteAccount.CreatedAt,
|
||||
UpdatedAt: remoteAccount.UpdatedAt,
|
||||
Username: remoteAccount.Username,
|
||||
Domain: remoteAccount.Domain,
|
||||
DisplayName: remoteAccount.DisplayName,
|
||||
URI: remoteAccount.URI,
|
||||
InboxURI: remoteAccount.URI,
|
||||
SharedInboxURI: remoteAccount.SharedInboxURI,
|
||||
PublicKeyURI: remoteAccount.PublicKeyURI,
|
||||
URL: remoteAccount.URL,
|
||||
FollowingURI: remoteAccount.FollowingURI,
|
||||
FollowersURI: remoteAccount.FollowersURI,
|
||||
OutboxURI: remoteAccount.OutboxURI,
|
||||
FeaturedCollectionURI: remoteAccount.FeaturedCollectionURI,
|
||||
Emojis: []*gtsmodel.Emoji{
|
||||
// dereference an emoji we don't have stored yet
|
||||
{
|
||||
URI: "http://fossbros-anonymous.io/emoji/01GD5HCC2YECT012TK8PAGX4D1",
|
||||
Shortcode: "kip_van_den_bos",
|
||||
UpdatedAt: testrig.TimeMustParse("2022-09-13T12:13:12+02:00"),
|
||||
ImageRemoteURL: "http://fossbros-anonymous.io/emoji/kip.gif",
|
||||
Disabled: testrig.FalseBool(),
|
||||
VisibleInPicker: testrig.FalseBool(),
|
||||
Domain: "fossbros-anonymous.io",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetchedAccount2, err := suite.dereferencer.GetRemoteAccount(context.Background(), dereferencing.GetRemoteAccountParams{
|
||||
RequestingUsername: fetchingAccount.Username,
|
||||
RemoteAccountID: testrig.URLMustParse(remoteAccount.URI),
|
||||
RemoteAccountHost: remoteAccount.Domain,
|
||||
RemoteAccountUsername: remoteAccount.Username,
|
||||
PartialAccount: remoteAccountPartial2,
|
||||
Blocking: true,
|
||||
})
|
||||
suite.NoError(err)
|
||||
suite.NotNil(fetchedAccount2)
|
||||
suite.NotNil(fetchedAccount2.EmojiIDs)
|
||||
suite.NotNil(fetchedAccount2.Emojis)
|
||||
suite.Equal("http://fossbros-anonymous.io/emoji/01GD5HCC2YECT012TK8PAGX4D1", fetchedAccount2.Emojis[0].URI)
|
||||
}
|
||||
|
||||
func TestAccountTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(AccountTestSuite))
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ type DereferencerStandardTestSuite struct {
|
||||
testRemoteServices map[string]vocab.ActivityStreamsService
|
||||
testRemoteAttachments map[string]testrig.RemoteAttachmentFile
|
||||
testAccounts map[string]*gtsmodel.Account
|
||||
testEmojis map[string]*gtsmodel.Emoji
|
||||
|
||||
dereferencer dereferencing.Dereferencer
|
||||
}
|
||||
@ -55,6 +56,7 @@ func (suite *DereferencerStandardTestSuite) SetupTest() {
|
||||
suite.testRemoteGroups = testrig.NewTestFediGroups()
|
||||
suite.testRemoteServices = testrig.NewTestFediServices()
|
||||
suite.testRemoteAttachments = testrig.NewTestFediAttachments("../../../testrig/media")
|
||||
suite.testEmojis = testrig.NewTestEmojis()
|
||||
|
||||
suite.db = testrig.NewTestDB()
|
||||
suite.storage = testrig.NewInMemoryStorage()
|
||||
|
@ -24,6 +24,10 @@
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
)
|
||||
|
||||
@ -49,3 +53,57 @@ func (d *deref) GetRemoteEmoji(ctx context.Context, requestingUsername string, r
|
||||
|
||||
return processingMedia, nil
|
||||
}
|
||||
|
||||
func (d *deref) populateEmojis(ctx context.Context, rawEmojis []*gtsmodel.Emoji, requestingUsername string) ([]*gtsmodel.Emoji, error) {
|
||||
// At this point we should know:
|
||||
// * the AP uri of the emoji
|
||||
// * the domain of the emoji
|
||||
// * the shortcode of the emoji
|
||||
// * the remote URL of the image
|
||||
// This should be enough to dereference the emoji
|
||||
|
||||
gotEmojis := make([]*gtsmodel.Emoji, 0, len(rawEmojis))
|
||||
|
||||
for _, e := range rawEmojis {
|
||||
var gotEmoji *gtsmodel.Emoji
|
||||
var err error
|
||||
|
||||
// check if we've already got this emoji in the db
|
||||
if gotEmoji, err = d.db.GetEmojiByURI(ctx, e.URI); err != nil && err != db.ErrNoEntries {
|
||||
log.Errorf("populateEmojis: error checking database for emoji %s: %s", e.URI, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if gotEmoji == nil {
|
||||
// it's new! go get it!
|
||||
newEmojiID, err := id.NewRandomULID()
|
||||
if err != nil {
|
||||
log.Errorf("populateEmojis: error generating id for remote emoji %s: %s", e.URI, err)
|
||||
continue
|
||||
}
|
||||
|
||||
processingEmoji, err := d.GetRemoteEmoji(ctx, requestingUsername, e.ImageRemoteURL, e.Shortcode, newEmojiID, e.URI, &media.AdditionalEmojiInfo{
|
||||
Domain: &e.Domain,
|
||||
ImageRemoteURL: &e.ImageRemoteURL,
|
||||
ImageStaticRemoteURL: &e.ImageRemoteURL,
|
||||
Disabled: e.Disabled,
|
||||
VisibleInPicker: e.VisibleInPicker,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("populateEmojis: couldn't get remote emoji %s: %s", e.URI, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if gotEmoji, err = processingEmoji.LoadEmoji(ctx); err != nil {
|
||||
log.Errorf("populateEmojis: couldn't load remote emoji %s: %s", e.URI, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here, we either had the emoji already or we successfully fetched it
|
||||
gotEmojis = append(gotEmojis, gotEmoji)
|
||||
}
|
||||
|
||||
return gotEmojis, nil
|
||||
}
|
||||
|
@ -406,58 +406,17 @@ func (d *deref) populateStatusAttachments(ctx context.Context, status *gtsmodel.
|
||||
}
|
||||
|
||||
func (d *deref) populateStatusEmojis(ctx context.Context, status *gtsmodel.Status, requestingUsername string) error {
|
||||
// At this point we should know:
|
||||
// * the AP uri of the emoji
|
||||
// * the domain of the emoji
|
||||
// * the shortcode of the emoji
|
||||
// * the remote URL of the image
|
||||
// This should be enough to dereference the emoji
|
||||
|
||||
gotEmojis := make([]*gtsmodel.Emoji, 0, len(status.Emojis))
|
||||
emojiIDs := make([]string, 0, len(status.Emojis))
|
||||
|
||||
for _, e := range status.Emojis {
|
||||
var gotEmoji *gtsmodel.Emoji
|
||||
var err error
|
||||
|
||||
// check if we've already got this emoji in the db
|
||||
if gotEmoji, err = d.db.GetEmojiByURI(ctx, e.URI); err != nil && err != db.ErrNoEntries {
|
||||
log.Errorf("populateStatusEmojis: error checking database for emoji %s: %s", e.URI, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if gotEmoji == nil {
|
||||
// it's new! go get it!
|
||||
newEmojiID, err := id.NewRandomULID()
|
||||
emojis, err := d.populateEmojis(ctx, status.Emojis, requestingUsername)
|
||||
if err != nil {
|
||||
log.Errorf("populateStatusEmojis: error generating id for remote emoji %s: %s", e.URI, err)
|
||||
continue
|
||||
return err
|
||||
}
|
||||
|
||||
processingEmoji, err := d.GetRemoteEmoji(ctx, requestingUsername, e.ImageRemoteURL, e.Shortcode, newEmojiID, e.URI, &media.AdditionalEmojiInfo{
|
||||
Domain: &e.Domain,
|
||||
ImageRemoteURL: &e.ImageRemoteURL,
|
||||
ImageStaticRemoteURL: &e.ImageRemoteURL,
|
||||
Disabled: e.Disabled,
|
||||
VisibleInPicker: e.VisibleInPicker,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("populateStatusEmojis: couldn't get remote emoji %s: %s", e.URI, err)
|
||||
continue
|
||||
emojiIDs := make([]string, 0, len(emojis))
|
||||
for _, e := range emojis {
|
||||
emojiIDs = append(emojiIDs, e.ID)
|
||||
}
|
||||
|
||||
if gotEmoji, err = processingEmoji.LoadEmoji(ctx); err != nil {
|
||||
log.Errorf("populateStatusEmojis: couldn't load remote emoji %s: %s", e.URI, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here, we either had the emoji already or we successfully fetched it
|
||||
gotEmojis = append(gotEmojis, gotEmoji)
|
||||
emojiIDs = append(emojiIDs, gotEmoji.ID)
|
||||
}
|
||||
|
||||
status.Emojis = gotEmojis
|
||||
status.Emojis = emojis
|
||||
status.EmojiIDs = emojiIDs
|
||||
return nil
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
||||
return fmt.Errorf("UPDATE: error converting to account: %s", err)
|
||||
}
|
||||
|
||||
if updatedAcct.Domain == config.GetHost() {
|
||||
if updatedAcct.Domain == config.GetHost() || updatedAcct.Domain == config.GetAccountDomain() {
|
||||
// no need to update local accounts
|
||||
// in fact, if we do this will break the shit out of things so do NOT
|
||||
return nil
|
||||
@ -136,13 +136,8 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
||||
updatedAcct.ID = requestingAcct.ID
|
||||
updatedAcct.Language = requestingAcct.Language
|
||||
|
||||
// do the update
|
||||
updatedAcct, err = f.db.UpdateAccount(ctx, updatedAcct)
|
||||
if err != nil {
|
||||
return fmt.Errorf("UPDATE: database error inserting updated account: %s", err)
|
||||
}
|
||||
|
||||
// pass to the processor for further processing of eg., avatar/header
|
||||
// pass to the processor for further updating of eg., avatar/header, emojis
|
||||
// the actual db insert/update will take place a bit later
|
||||
f.fedWorker.Queue(messages.FromFederator{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityUpdate,
|
||||
|
@ -41,6 +41,8 @@ type Account struct {
|
||||
HeaderMediaAttachment *MediaAttachment `validate:"-" bun:"rel:belongs-to"` // MediaAttachment corresponding to headerMediaAttachmentID
|
||||
HeaderRemoteURL string `validate:"omitempty,url" bun:",nullzero"` // For a non-local account, where can the header be fetched?
|
||||
DisplayName string `validate:"-" bun:""` // DisplayName for this account. Can be empty, then just the Username will be used for display purposes.
|
||||
EmojiIDs []string `validate:"dive,ulid" bun:"emojis,array"` // Database IDs of any emojis used in this account's bio, display name, etc
|
||||
Emojis []*Emoji `validate:"-" bun:"attached_emojis,m2m:account_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
Fields []Field `validate:"-"` // a key/value map of fields that this account has added to their profile
|
||||
Note string `validate:"-" bun:""` // A note that this account has on their profile (ie., the account's bio/description of themselves)
|
||||
NoteRaw string `validate:"-" bun:""` // The raw contents of .Note without conversion to HTML, only available when requester = target
|
||||
@ -76,6 +78,14 @@ type Account struct {
|
||||
SuspensionOrigin string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID
|
||||
}
|
||||
|
||||
// AccountToEmoji is an intermediate struct to facilitate the many2many relationship between an account and one or more emojis.
|
||||
type AccountToEmoji struct {
|
||||
AccountID string `validate:"ulid,required" bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
|
||||
Account *Account `validate:"-" bun:"rel:belongs-to"`
|
||||
EmojiID string `validate:"ulid,required" bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
|
||||
Emoji *Emoji `validate:"-" bun:"rel:belongs-to"`
|
||||
}
|
||||
|
||||
// Field represents a key value field on an account, for things like pronouns, website, etc.
|
||||
// VerifiedAt is optional, to be used only if Value is a URL to a webpage that contains the
|
||||
// username of the user.
|
||||
|
@ -259,6 +259,8 @@ func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origi
|
||||
account.HeaderMediaAttachmentID = ""
|
||||
account.HeaderRemoteURL = ""
|
||||
account.Reason = ""
|
||||
account.Emojis = []*gtsmodel.Emoji{}
|
||||
account.EmojiIDs = []string{}
|
||||
account.Fields = []gtsmodel.Field{}
|
||||
hideCollections := true
|
||||
account.HideCollections = &hideCollections
|
||||
|
@ -27,6 +27,7 @@
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
@ -46,11 +47,14 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||
account.Bot = form.Bot
|
||||
}
|
||||
|
||||
var updateEmojis bool
|
||||
|
||||
if form.DisplayName != nil {
|
||||
if err := validate.DisplayName(*form.DisplayName); err != nil {
|
||||
return nil, gtserror.NewErrorBadRequest(err)
|
||||
}
|
||||
account.DisplayName = text.SanitizePlaintext(*form.DisplayName)
|
||||
updateEmojis = true
|
||||
}
|
||||
|
||||
if form.Note != nil {
|
||||
@ -69,6 +73,30 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||
|
||||
// Set updated HTML-ified note
|
||||
account.Note = note
|
||||
updateEmojis = true
|
||||
}
|
||||
|
||||
if updateEmojis {
|
||||
// account emojis -- treat the sanitized display name and raw
|
||||
// note like one long text for the purposes of deriving emojis
|
||||
accountEmojiShortcodes := util.DeriveEmojisFromText(account.DisplayName + "\n\n" + account.NoteRaw)
|
||||
account.Emojis = make([]*gtsmodel.Emoji, 0, len(accountEmojiShortcodes))
|
||||
account.EmojiIDs = make([]string, 0, len(accountEmojiShortcodes))
|
||||
|
||||
for _, shortcode := range accountEmojiShortcodes {
|
||||
emoji, err := p.db.GetEmojiByShortcodeDomain(ctx, shortcode, "")
|
||||
if err != nil {
|
||||
if err != db.ErrNoEntries {
|
||||
log.Errorf("error getting local emoji with shortcode %s: %s", shortcode, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if *emoji.VisibleInPicker && !*emoji.Disabled {
|
||||
account.Emojis = append(account.Emojis, emoji)
|
||||
account.EmojiIDs = append(account.EmojiIDs, emoji.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if form.Avatar != nil && form.Avatar.Size != 0 {
|
||||
|
@ -369,9 +369,13 @@ func (p *processor) processUpdateAccountFromFederator(ctx context.Context, feder
|
||||
return err
|
||||
}
|
||||
|
||||
// further database updates occur inside getremoteaccount
|
||||
if _, err := p.federator.GetRemoteAccount(ctx, dereferencing.GetRemoteAccountParams{
|
||||
RequestingUsername: federatorMsg.ReceivingAccount.Username,
|
||||
RemoteAccountID: incomingAccountURL,
|
||||
RemoteAccountHost: incomingAccount.Domain,
|
||||
RemoteAccountUsername: incomingAccount.Username,
|
||||
PartialAccount: incomingAccount,
|
||||
Blocking: true,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error enriching updated account from federator: %s", err)
|
||||
|
@ -88,6 +88,13 @@ func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable a
|
||||
acct.DisplayName = displayName
|
||||
}
|
||||
|
||||
// account emojis (used in bio, display name, fields)
|
||||
if emojis, err := ap.ExtractEmojis(accountable); err != nil {
|
||||
log.Infof("ASRepresentationToAccount: error extracting account emojis: %s", err)
|
||||
} else {
|
||||
acct.Emojis = emojis
|
||||
}
|
||||
|
||||
// TODO: fields aka attachment array
|
||||
|
||||
// note aka summary
|
||||
|
@ -473,6 +473,7 @@ type TypeUtilsTestSuite struct {
|
||||
testAccounts map[string]*gtsmodel.Account
|
||||
testStatuses map[string]*gtsmodel.Status
|
||||
testPeople map[string]vocab.ActivityStreamsPerson
|
||||
testEmojis map[string]*gtsmodel.Emoji
|
||||
|
||||
typeconverter typeutils.TypeConverter
|
||||
}
|
||||
@ -485,6 +486,7 @@ func (suite *TypeUtilsTestSuite) SetupSuite() {
|
||||
suite.testAccounts = testrig.NewTestAccounts()
|
||||
suite.testStatuses = testrig.NewTestStatuses()
|
||||
suite.testPeople = testrig.NewTestFediPeople()
|
||||
suite.testEmojis = testrig.NewTestEmojis()
|
||||
suite.typeconverter = typeutils.NewConverter(suite.db)
|
||||
}
|
||||
|
||||
|
@ -216,8 +216,33 @@ func (c *converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
|
||||
// set the public key property on the Person
|
||||
person.SetW3IDSecurityV1PublicKey(publicKeyProp)
|
||||
|
||||
// tag
|
||||
// TODO: Any tags used in the summary of this profile
|
||||
// tags
|
||||
tagProp := streams.NewActivityStreamsTagProperty()
|
||||
|
||||
// tag -- emojis
|
||||
emojis := a.Emojis
|
||||
if len(a.EmojiIDs) > len(emojis) {
|
||||
emojis = []*gtsmodel.Emoji{}
|
||||
for _, emojiID := range a.EmojiIDs {
|
||||
emoji, err := c.db.GetEmojiByID(ctx, emojiID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AccountToAS: error getting emoji %s from database: %s", emojiID, err)
|
||||
}
|
||||
emojis = append(emojis, emoji)
|
||||
}
|
||||
}
|
||||
for _, emoji := range emojis {
|
||||
asEmoji, err := c.EmojiToAS(ctx, emoji)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AccountToAS: error converting emoji to AS emoji: %s", err)
|
||||
}
|
||||
tagProp.AppendTootEmoji(asEmoji)
|
||||
}
|
||||
|
||||
// tag -- hashtags
|
||||
// TODO
|
||||
|
||||
person.SetActivityStreamsTag(tagProp)
|
||||
|
||||
// attachment
|
||||
// Used for profile fields.
|
||||
@ -477,11 +502,11 @@ func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.A
|
||||
}
|
||||
}
|
||||
for _, emoji := range emojis {
|
||||
asMention, err := c.EmojiToAS(ctx, emoji)
|
||||
asEmoji, err := c.EmojiToAS(ctx, emoji)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("StatusToAS: error converting emoji to AS emoji: %s", err)
|
||||
}
|
||||
tagProp.AppendTootEmoji(asMention)
|
||||
tagProp.AppendTootEmoji(asEmoji)
|
||||
}
|
||||
|
||||
// tag -- hashtags
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/activity/streams"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
@ -34,7 +35,8 @@ type InternalToASTestSuite struct {
|
||||
}
|
||||
|
||||
func (suite *InternalToASTestSuite) TestAccountToAS() {
|
||||
testAccount := suite.testAccounts["local_account_1"] // take zork for this test
|
||||
testAccount := >smodel.Account{}
|
||||
*testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
|
||||
|
||||
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
|
||||
suite.NoError(err)
|
||||
@ -49,11 +51,33 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
|
||||
// this is necessary because the order of multiple 'context' entries is not determinate
|
||||
trimmed := strings.Split(string(bytes), "\"discoverable\"")[1]
|
||||
|
||||
suite.Equal(`:true,"featured":"http://localhost:8080/users/the_mighty_zork/collections/featured","followers":"http://localhost:8080/users/the_mighty_zork/followers","following":"http://localhost:8080/users/the_mighty_zork/following","icon":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg"},"id":"http://localhost:8080/users/the_mighty_zork","image":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg"},"inbox":"http://localhost:8080/users/the_mighty_zork/inbox","manuallyApprovesFollowers":false,"name":"original zork (he/they)","outbox":"http://localhost:8080/users/the_mighty_zork/outbox","preferredUsername":"the_mighty_zork","publicKey":{"id":"http://localhost:8080/users/the_mighty_zork/main-key","owner":"http://localhost:8080/users/the_mighty_zork","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"},"summary":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","type":"Person","url":"http://localhost:8080/@the_mighty_zork"}`, trimmed)
|
||||
suite.Equal(`:true,"featured":"http://localhost:8080/users/the_mighty_zork/collections/featured","followers":"http://localhost:8080/users/the_mighty_zork/followers","following":"http://localhost:8080/users/the_mighty_zork/following","icon":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg"},"id":"http://localhost:8080/users/the_mighty_zork","image":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg"},"inbox":"http://localhost:8080/users/the_mighty_zork/inbox","manuallyApprovesFollowers":false,"name":"original zork (he/they)","outbox":"http://localhost:8080/users/the_mighty_zork/outbox","preferredUsername":"the_mighty_zork","publicKey":{"id":"http://localhost:8080/users/the_mighty_zork/main-key","owner":"http://localhost:8080/users/the_mighty_zork","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"},"summary":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","tag":[],"type":"Person","url":"http://localhost:8080/@the_mighty_zork"}`, trimmed)
|
||||
}
|
||||
|
||||
func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() {
|
||||
testAccount := >smodel.Account{}
|
||||
*testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
|
||||
testAccount.Emojis = []*gtsmodel.Emoji{suite.testEmojis["rainbow"]}
|
||||
|
||||
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
|
||||
suite.NoError(err)
|
||||
|
||||
ser, err := streams.Serialize(asPerson)
|
||||
suite.NoError(err)
|
||||
|
||||
bytes, err := json.Marshal(ser)
|
||||
suite.NoError(err)
|
||||
|
||||
// trim off everything up to 'discoverable';
|
||||
// this is necessary because the order of multiple 'context' entries is not determinate
|
||||
trimmed := strings.Split(string(bytes), "\"discoverable\"")[1]
|
||||
|
||||
suite.Equal(`:true,"featured":"http://localhost:8080/users/the_mighty_zork/collections/featured","followers":"http://localhost:8080/users/the_mighty_zork/followers","following":"http://localhost:8080/users/the_mighty_zork/following","icon":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg"},"id":"http://localhost:8080/users/the_mighty_zork","image":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg"},"inbox":"http://localhost:8080/users/the_mighty_zork/inbox","manuallyApprovesFollowers":false,"name":"original zork (he/they)","outbox":"http://localhost:8080/users/the_mighty_zork/outbox","preferredUsername":"the_mighty_zork","publicKey":{"id":"http://localhost:8080/users/the_mighty_zork/main-key","owner":"http://localhost:8080/users/the_mighty_zork","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"},"summary":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","tag":{"icon":{"mediaType":"image/png","type":"Image","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png"},"id":"http://localhost:8080/emoji/01F8MH9H8E4VG3KDYJR9EGPXCQ","name":":rainbow:","type":"Emoji"},"type":"Person","url":"http://localhost:8080/@the_mighty_zork"}`, trimmed)
|
||||
}
|
||||
|
||||
func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() {
|
||||
testAccount := suite.testAccounts["local_account_1"] // take zork for this test
|
||||
testAccount := >smodel.Account{}
|
||||
*testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
|
||||
sharedInbox := "http://localhost:8080/sharedInbox"
|
||||
testAccount.SharedInboxURI = &sharedInbox
|
||||
|
||||
@ -70,7 +94,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() {
|
||||
// this is necessary because the order of multiple 'context' entries is not determinate
|
||||
trimmed := strings.Split(string(bytes), "\"discoverable\"")[1]
|
||||
|
||||
suite.Equal(`:true,"endpoints":{"sharedInbox":"http://localhost:8080/sharedInbox"},"featured":"http://localhost:8080/users/the_mighty_zork/collections/featured","followers":"http://localhost:8080/users/the_mighty_zork/followers","following":"http://localhost:8080/users/the_mighty_zork/following","icon":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg"},"id":"http://localhost:8080/users/the_mighty_zork","image":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg"},"inbox":"http://localhost:8080/users/the_mighty_zork/inbox","manuallyApprovesFollowers":false,"name":"original zork (he/they)","outbox":"http://localhost:8080/users/the_mighty_zork/outbox","preferredUsername":"the_mighty_zork","publicKey":{"id":"http://localhost:8080/users/the_mighty_zork/main-key","owner":"http://localhost:8080/users/the_mighty_zork","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"},"summary":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","type":"Person","url":"http://localhost:8080/@the_mighty_zork"}`, trimmed)
|
||||
suite.Equal(`:true,"endpoints":{"sharedInbox":"http://localhost:8080/sharedInbox"},"featured":"http://localhost:8080/users/the_mighty_zork/collections/featured","followers":"http://localhost:8080/users/the_mighty_zork/followers","following":"http://localhost:8080/users/the_mighty_zork/following","icon":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg"},"id":"http://localhost:8080/users/the_mighty_zork","image":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg"},"inbox":"http://localhost:8080/users/the_mighty_zork/inbox","manuallyApprovesFollowers":false,"name":"original zork (he/they)","outbox":"http://localhost:8080/users/the_mighty_zork/outbox","preferredUsername":"the_mighty_zork","publicKey":{"id":"http://localhost:8080/users/the_mighty_zork/main-key","owner":"http://localhost:8080/users/the_mighty_zork","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"},"summary":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","tag":[],"type":"Person","url":"http://localhost:8080/@the_mighty_zork"}`, trimmed)
|
||||
}
|
||||
|
||||
func (suite *InternalToASTestSuite) TestOutboxToASCollection() {
|
||||
|
@ -159,8 +159,29 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
||||
fields = append(fields, mField)
|
||||
}
|
||||
|
||||
// account emojis
|
||||
emojis := []model.Emoji{}
|
||||
// TODO: account emojis
|
||||
gtsEmojis := a.Emojis
|
||||
if len(a.EmojiIDs) > len(gtsEmojis) {
|
||||
gtsEmojis = []*gtsmodel.Emoji{}
|
||||
for _, emojiID := range a.EmojiIDs {
|
||||
emoji, err := c.db.GetEmojiByID(ctx, emojiID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AccountToAPIAccountPublic: error getting emoji %s from database: %s", emojiID, err)
|
||||
}
|
||||
gtsEmojis = append(gtsEmojis, emoji)
|
||||
}
|
||||
}
|
||||
for _, emoji := range gtsEmojis {
|
||||
if *emoji.Disabled {
|
||||
continue
|
||||
}
|
||||
apiEmoji, err := c.EmojiToAPIEmoji(ctx, emoji)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AccountToAPIAccountPublic: error converting emoji to api emoji: %s", err)
|
||||
}
|
||||
emojis = append(emojis, apiEmoji)
|
||||
}
|
||||
|
||||
var acct string
|
||||
if a.Domain != "" {
|
||||
@ -194,7 +215,7 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
||||
FollowingCount: followingCount,
|
||||
StatusesCount: statusesCount,
|
||||
LastStatusAt: lastStatusAt,
|
||||
Emojis: emojis, // TODO: implement this
|
||||
Emojis: emojis,
|
||||
Fields: fields,
|
||||
Suspended: suspended,
|
||||
CustomCSS: a.CustomCSS,
|
||||
|
@ -43,6 +43,36 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontend() {
|
||||
suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","emojis":[],"fields":[]}`, string(b))
|
||||
}
|
||||
|
||||
func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiStruct() {
|
||||
testAccount := suite.testAccounts["local_account_1"] // take zork for this test
|
||||
testEmoji := suite.testEmojis["rainbow"]
|
||||
|
||||
testAccount.Emojis = []*gtsmodel.Emoji{testEmoji}
|
||||
|
||||
apiAccount, err := suite.typeconverter.AccountToAPIAccountPublic(context.Background(), testAccount)
|
||||
suite.NoError(err)
|
||||
suite.NotNil(apiAccount)
|
||||
|
||||
b, err := json.Marshal(apiAccount)
|
||||
suite.NoError(err)
|
||||
suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","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}],"fields":[]}`, string(b))
|
||||
}
|
||||
|
||||
func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiIDs() {
|
||||
testAccount := suite.testAccounts["local_account_1"] // take zork for this test
|
||||
testEmoji := suite.testEmojis["rainbow"]
|
||||
|
||||
testAccount.EmojiIDs = []string{testEmoji.ID}
|
||||
|
||||
apiAccount, err := suite.typeconverter.AccountToAPIAccountPublic(context.Background(), testAccount)
|
||||
suite.NoError(err)
|
||||
suite.NotNil(apiAccount)
|
||||
|
||||
b, err := json.Marshal(apiAccount)
|
||||
suite.NoError(err)
|
||||
suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","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}],"fields":[]}`, string(b))
|
||||
}
|
||||
|
||||
func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() {
|
||||
testAccount := suite.testAccounts["local_account_1"] // take zork for this test
|
||||
apiAccount, err := suite.typeconverter.AccountToAPIAccountSensitive(context.Background(), testAccount)
|
||||
|
@ -32,6 +32,7 @@
|
||||
|
||||
var testModels = []interface{}{
|
||||
>smodel.Account{},
|
||||
>smodel.AccountToEmoji{},
|
||||
>smodel.Application{},
|
||||
>smodel.Block{},
|
||||
>smodel.DomainBlock{},
|
||||
|
BIN
testrig/media/kip-original.gif
Normal file
BIN
testrig/media/kip-original.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
testrig/media/kip-static.png
Normal file
BIN
testrig/media/kip-static.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 802 B |
BIN
testrig/media/yell-original.png
Normal file
BIN
testrig/media/yell-original.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
testrig/media/yell-static.png
Normal file
BIN
testrig/media/yell-static.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -952,6 +952,28 @@ func NewTestEmojis() map[string]*gtsmodel.Emoji {
|
||||
VisibleInPicker: TrueBool(),
|
||||
CategoryID: "",
|
||||
},
|
||||
"yell": {
|
||||
ID: "01GD5KP5CQEE1R3X43Y1EHS2CW",
|
||||
Shortcode: "yell",
|
||||
Domain: "fossbros-anonymous.io",
|
||||
CreatedAt: TimeMustParse("2020-03-18T13:12:00+01:00"),
|
||||
UpdatedAt: TimeMustParse("2020-03-18T13:12:00+01:00"),
|
||||
ImageRemoteURL: "http://fossbros-anonymous.io/emoji/yell.gif",
|
||||
ImageStaticRemoteURL: "",
|
||||
ImageURL: "http://localhost:8080/fileserver/01GD5KR15NHTY8FZ01CD4D08XP/emoji/original/01GD5KP5CQEE1R3X43Y1EHS2CW.png",
|
||||
ImagePath: "/tmp/gotosocial/01GD5KR15NHTY8FZ01CD4D08XP/emoji/original/01GD5KP5CQEE1R3X43Y1EHS2CW.png",
|
||||
ImageStaticURL: "http://localhost:8080/fileserver/01GD5KR15NHTY8FZ01CD4D08XP/emoji/static/01GD5KP5CQEE1R3X43Y1EHS2CW.png",
|
||||
ImageStaticPath: "/tmp/gotosocial/01GD5KR15NHTY8FZ01CD4D08XP/emoji/static/01GD5KP5CQEE1R3X43Y1EHS2CW.png",
|
||||
ImageContentType: "image/png",
|
||||
ImageStaticContentType: "image/png",
|
||||
ImageFileSize: 10889,
|
||||
ImageStaticFileSize: 10808,
|
||||
ImageUpdatedAt: TimeMustParse("2020-03-18T13:12:00+01:00"),
|
||||
Disabled: FalseBool(),
|
||||
URI: "http://fossbros-anonymous.io/emoji/01GD5KP5CQEE1R3X43Y1EHS2CW",
|
||||
VisibleInPicker: FalseBool(),
|
||||
CategoryID: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1045,6 +1067,10 @@ func newTestStoredEmoji() map[string]filenames {
|
||||
Original: "rainbow-original.png",
|
||||
Static: "rainbow-static.png",
|
||||
},
|
||||
"yell": {
|
||||
Original: "yell-original.png",
|
||||
Static: "yell-static.png",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1941,6 +1967,22 @@ func NewTestFediServices() map[string]vocab.ActivityStreamsService {
|
||||
}
|
||||
}
|
||||
|
||||
func NewTestFediEmojis() map[string]vocab.TootEmoji {
|
||||
return map[string]vocab.TootEmoji{
|
||||
"http://fossbros-anonymous.io/emoji/01GD5HCC2YECT012TK8PAGX4D1": newAPEmoji(
|
||||
URLMustParse("http://fossbros-anonymous.io/emoji/01GD5HCC2YECT012TK8PAGX4D1"),
|
||||
"kip_van_den_bos",
|
||||
TimeMustParse("2022-09-13T12:13:12+02:00"),
|
||||
newAPImage(
|
||||
URLMustParse("http://fossbros-anonymous.io/emoji/kip.gif"),
|
||||
"image/gif",
|
||||
"",
|
||||
"",
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// RemoteAttachmentFile mimics a remote (federated) attachment
|
||||
type RemoteAttachmentFile struct {
|
||||
Data []byte
|
||||
@ -1968,6 +2010,16 @@ func NewTestFediAttachments(relativePath string) map[string]RemoteAttachmentFile
|
||||
panic(err)
|
||||
}
|
||||
|
||||
kipBytes, err := os.ReadFile(fmt.Sprintf("%s/kip-original.gif", relativePath))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
yellBytes, err := os.ReadFile(fmt.Sprintf("%s/yell-original.png", relativePath))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return map[string]RemoteAttachmentFile{
|
||||
"https://s3-us-west-2.amazonaws.com/plushcity/media_attachments/files/106/867/380/219/163/828/original/88e8758c5f011439.jpg": {
|
||||
Data: beeBytes,
|
||||
@ -1985,6 +2037,14 @@ func NewTestFediAttachments(relativePath string) map[string]RemoteAttachmentFile
|
||||
Data: peglinBytes,
|
||||
ContentType: "image/gif",
|
||||
},
|
||||
"http://fossbros-anonymous.io/emoji/kip.gif": {
|
||||
Data: kipBytes,
|
||||
ContentType: "image/gif",
|
||||
},
|
||||
"http://fossbros-anonymous.io/emoji/yell.gif": {
|
||||
Data: yellBytes,
|
||||
ContentType: "image/png",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -2857,6 +2917,28 @@ func newAPImage(url *url.URL, mediaType string, imageDescription string, blurhas
|
||||
return image
|
||||
}
|
||||
|
||||
func newAPEmoji(id *url.URL, name string, updated time.Time, image vocab.ActivityStreamsImage) vocab.TootEmoji {
|
||||
emoji := streams.NewTootEmoji()
|
||||
|
||||
idProp := streams.NewJSONLDIdProperty()
|
||||
idProp.SetIRI(id)
|
||||
emoji.SetJSONLDId(idProp)
|
||||
|
||||
nameProp := streams.NewActivityStreamsNameProperty()
|
||||
nameProp.AppendXMLSchemaString(`:` + strings.Trim(name, ":") + `:`)
|
||||
emoji.SetActivityStreamsName(nameProp)
|
||||
|
||||
updatedProp := streams.NewActivityStreamsUpdatedProperty()
|
||||
updatedProp.Set(updated)
|
||||
emoji.SetActivityStreamsUpdated(updatedProp)
|
||||
|
||||
iconProp := streams.NewActivityStreamsIconProperty()
|
||||
iconProp.AppendActivityStreamsImage(image)
|
||||
emoji.SetActivityStreamsIcon(iconProp)
|
||||
|
||||
return emoji
|
||||
}
|
||||
|
||||
// NewAPNote returns a new activity streams note for the given parameters
|
||||
func NewAPNote(
|
||||
noteID *url.URL,
|
||||
|
@ -64,6 +64,7 @@ type MockHTTPClient struct {
|
||||
testRemoteGroups map[string]vocab.ActivityStreamsGroup
|
||||
testRemoteServices map[string]vocab.ActivityStreamsService
|
||||
testRemoteAttachments map[string]RemoteAttachmentFile
|
||||
testRemoteEmojis map[string]vocab.TootEmoji
|
||||
|
||||
SentMessages sync.Map
|
||||
}
|
||||
@ -90,6 +91,7 @@ func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error), relat
|
||||
mockHTTPClient.testRemoteGroups = NewTestFediGroups()
|
||||
mockHTTPClient.testRemoteServices = NewTestFediServices()
|
||||
mockHTTPClient.testRemoteAttachments = NewTestFediAttachments(relativeMediaPath)
|
||||
mockHTTPClient.testRemoteEmojis = NewTestFediEmojis()
|
||||
|
||||
mockHTTPClient.do = func(req *http.Request) (*http.Response, error) {
|
||||
responseCode := http.StatusNotFound
|
||||
@ -173,6 +175,19 @@ func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error), relat
|
||||
responseBytes = serviceJSON
|
||||
responseContentType = applicationActivityJSON
|
||||
responseContentLength = len(serviceJSON)
|
||||
} else if emoji, ok := mockHTTPClient.testRemoteEmojis[req.URL.String()]; ok {
|
||||
emojiI, err := streams.Serialize(emoji)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
emojiJSON, err := json.Marshal(emojiI)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
responseCode = http.StatusOK
|
||||
responseBytes = emojiJSON
|
||||
responseContentType = applicationActivityJSON
|
||||
responseContentLength = len(emojiJSON)
|
||||
} else if attachment, ok := mockHTTPClient.testRemoteAttachments[req.URL.String()]; ok {
|
||||
responseCode = http.StatusOK
|
||||
responseBytes = attachment.Data
|
||||
|
@ -12,12 +12,12 @@
|
||||
<div class="basic">
|
||||
<div id="profile-basic-filler2"></div>
|
||||
<a href="{{.account.Avatar}}" class="avatar"><img src="{{.account.Avatar}}" alt="{{if .account.DisplayName}}{{.account.DisplayName}}{{else}}{{.account.Username}}{{end}}'s avatar"></a>
|
||||
<div class="displayname">{{if .account.DisplayName}}{{.account.DisplayName}}{{else}}{{.account.Username}}{{end}}</div>
|
||||
<div class="displayname">{{if .account.DisplayName}}{{emojify .account.Emojis (escape .account.DisplayName)}}{{else}}{{.account.Username}}{{end}}</div>
|
||||
<div class="username"><span>@{{.account.Username}}</span><span>@{{.instance.AccountDomain}}</span></div>
|
||||
</div>
|
||||
<div class="detailed">
|
||||
<div class="bio">
|
||||
{{ if .account.Note }}{{ .account.Note | noescape }}{{else}}This GoToSocial user hasn't written a bio yet!{{end}}
|
||||
{{ if .account.Note }}{{emojify .account.Emojis (noescape .account.Note)}}{{else}}This GoToSocial user hasn't written a bio yet!{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="accountstats">
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="contentgrid">
|
||||
<a href="{{.Account.URL}}" class="avatar"><img src="{{.Account.Avatar}}" alt=""></a>
|
||||
<a href="{{.Account.URL}}" class="displayname">{{if .Account.DisplayName}}{{.Account.DisplayName}}{{else}}{{.Account.Username}}{{end}}</a>
|
||||
<a href="{{.Account.URL}}" class="displayname">{{if .Account.DisplayName}}{{emojify .Account.Emojis (escape .Account.DisplayName)}}{{else}}{{.Account.Username}}{{end}}</a>
|
||||
<a href="{{.Account.URL}}" class="username">@{{.Account.Acct}}</a>
|
||||
<div class="text">
|
||||
{{if .SpoilerText}}
|
||||
|
Loading…
Reference in New Issue
Block a user