mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-24 10:15:47 +03: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
34 changed files with 934 additions and 127 deletions
|
@ -41,6 +41,7 @@ type Accountable interface {
|
||||||
WithFeatured
|
WithFeatured
|
||||||
WithManuallyApprovesFollowers
|
WithManuallyApprovesFollowers
|
||||||
WithEndpoints
|
WithEndpoints
|
||||||
|
WithTag
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statusable represents the minimum activitypub interface for representing a 'status'.
|
// Statusable represents the minimum activitypub interface for representing a 'status'.
|
||||||
|
|
|
@ -200,7 +200,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerGet
|
||||||
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerTwoFields() {
|
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerTwoFields() {
|
||||||
// set up the request
|
// set up the request
|
||||||
// we're updating the note of zork, and setting locked to true
|
// 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(
|
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||||
"", "",
|
"", "",
|
||||||
map[string]string{
|
map[string]string{
|
||||||
|
@ -235,9 +235,19 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerTwo
|
||||||
|
|
||||||
// check the returned api model account
|
// check the returned api model account
|
||||||
// fields should be updated
|
// 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.Equal(newBio, apimodelAccount.Source.Note)
|
||||||
suite.True(apimodelAccount.Locked)
|
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() {
|
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerWithMedia() {
|
||||||
|
|
|
@ -237,6 +237,8 @@ func (suite *InboxPostTestSuite) TestPostUnblock() {
|
||||||
func (suite *InboxPostTestSuite) TestPostUpdate() {
|
func (suite *InboxPostTestSuite) TestPostUpdate() {
|
||||||
updatedAccount := *suite.testAccounts["remote_account_1"]
|
updatedAccount := *suite.testAccounts["remote_account_1"]
|
||||||
updatedAccount.DisplayName = "updated display name!"
|
updatedAccount.DisplayName = "updated display name!"
|
||||||
|
testEmoji := testrig.NewTestEmojis()["rainbow"]
|
||||||
|
updatedAccount.Emojis = []*gtsmodel.Emoji{testEmoji}
|
||||||
|
|
||||||
asAccount, err := suite.tc.AccountToAS(context.Background(), &updatedAccount)
|
asAccount, err := suite.tc.AccountToAS(context.Background(), &updatedAccount)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
@ -288,6 +290,15 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
|
||||||
federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker)
|
federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker)
|
||||||
emailSender := testrig.NewEmailSender("../../../../web/template/", nil)
|
emailSender := testrig.NewEmailSender("../../../../web/template/", nil)
|
||||||
processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker)
|
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)
|
userModule := user.New(processor).(*user.Module)
|
||||||
|
|
||||||
// setup request
|
// setup request
|
||||||
|
@ -322,11 +333,21 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
|
||||||
suite.Equal(http.StatusOK, result.StatusCode)
|
suite.Equal(http.StatusOK, result.StatusCode)
|
||||||
|
|
||||||
// account should be changed in the database now
|
// account should be changed in the database now
|
||||||
dbUpdatedAccount, err := suite.db.GetAccountByID(context.Background(), updatedAccount.ID)
|
var dbUpdatedAccount *gtsmodel.Account
|
||||||
suite.NoError(err)
|
|
||||||
|
|
||||||
// displayName should be updated
|
if !testrig.WaitFor(func() bool {
|
||||||
suite.Equal("updated display name!", dbUpdatedAccount.DisplayName)
|
// displayName should be updated
|
||||||
|
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
|
// everything else should be the same as it was before
|
||||||
suite.EqualValues(updatedAccount.Username, dbUpdatedAccount.Username)
|
suite.EqualValues(updatedAccount.Username, dbUpdatedAccount.Username)
|
||||||
|
@ -350,7 +371,6 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
|
||||||
suite.EqualValues(updatedAccount.Language, dbUpdatedAccount.Language)
|
suite.EqualValues(updatedAccount.Language, dbUpdatedAccount.Language)
|
||||||
suite.EqualValues(updatedAccount.URI, dbUpdatedAccount.URI)
|
suite.EqualValues(updatedAccount.URI, dbUpdatedAccount.URI)
|
||||||
suite.EqualValues(updatedAccount.URL, dbUpdatedAccount.URL)
|
suite.EqualValues(updatedAccount.URL, dbUpdatedAccount.URL)
|
||||||
suite.EqualValues(updatedAccount.LastWebfingeredAt, dbUpdatedAccount.LastWebfingeredAt)
|
|
||||||
suite.EqualValues(updatedAccount.InboxURI, dbUpdatedAccount.InboxURI)
|
suite.EqualValues(updatedAccount.InboxURI, dbUpdatedAccount.InboxURI)
|
||||||
suite.EqualValues(updatedAccount.OutboxURI, dbUpdatedAccount.OutboxURI)
|
suite.EqualValues(updatedAccount.OutboxURI, dbUpdatedAccount.OutboxURI)
|
||||||
suite.EqualValues(updatedAccount.FollowingURI, dbUpdatedAccount.FollowingURI)
|
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,
|
HeaderMediaAttachment: nil,
|
||||||
HeaderRemoteURL: account.HeaderRemoteURL,
|
HeaderRemoteURL: account.HeaderRemoteURL,
|
||||||
DisplayName: account.DisplayName,
|
DisplayName: account.DisplayName,
|
||||||
|
EmojiIDs: account.EmojiIDs,
|
||||||
|
Emojis: nil,
|
||||||
Fields: account.Fields,
|
Fields: account.Fields,
|
||||||
Note: account.Note,
|
Note: account.Note,
|
||||||
NoteRaw: account.NoteRaw,
|
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 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)
|
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 updates one account by ID.
|
||||||
UpdateAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, Error)
|
UpdateAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, Error)
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,8 @@ func (a *accountDB) newAccountQ(account *gtsmodel.Account) *bun.SelectQuery {
|
||||||
NewSelect().
|
NewSelect().
|
||||||
Model(account).
|
Model(account).
|
||||||
Relation("AvatarMediaAttachment").
|
Relation("AvatarMediaAttachment").
|
||||||
Relation("HeaderMediaAttachment")
|
Relation("HeaderMediaAttachment").
|
||||||
|
Relation("Emojis")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *accountDB) GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, db.Error) {
|
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
|
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) {
|
func (a *accountDB) UpdateAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, db.Error) {
|
||||||
// Update the account's last-updated
|
// Update the account's last-updated
|
||||||
account.UpdatedAt = time.Now()
|
account.UpdatedAt = time.Now()
|
||||||
|
|
||||||
// Update the account model in the DB
|
if err := a.conn.RunInTx(ctx, func(tx bun.Tx) error {
|
||||||
_, err := a.conn.
|
// create links between this account and any emojis it uses
|
||||||
NewUpdate().
|
// first clear out any old emoji links
|
||||||
Model(account).
|
if _, err := tx.NewDelete().
|
||||||
WherePK().
|
Model(&[]*gtsmodel.AccountToEmoji{}).
|
||||||
Exec(ctx)
|
Where("account_id = ?", account.ID).
|
||||||
if err != nil {
|
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)
|
return nil, a.conn.ProcessError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Place updated account in cache
|
|
||||||
// (this will replace existing, i.e. invalidating)
|
|
||||||
a.cache.Put(account)
|
a.cache.Put(account)
|
||||||
|
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,9 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccountTestSuite struct {
|
type AccountTestSuite struct {
|
||||||
|
@ -71,17 +73,70 @@ func (suite *AccountTestSuite) TestGetAccountByUsernameDomain() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccountTestSuite) TestUpdateAccount() {
|
func (suite *AccountTestSuite) TestUpdateAccount() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
testAccount := suite.testAccounts["local_account_1"]
|
testAccount := suite.testAccounts["local_account_1"]
|
||||||
|
|
||||||
testAccount.DisplayName = "new display name!"
|
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)
|
suite.NoError(err)
|
||||||
|
|
||||||
updated, err := suite.db.GetAccountByID(context.Background(), testAccount.ID)
|
updated, err := suite.db.GetAccountByID(ctx, testAccount.ID)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal("new display name!", updated.DisplayName)
|
suite.Equal("new display name!", updated.DisplayName)
|
||||||
|
suite.Equal([]string{"01GD36ZKWTKY3T1JJ24JR7KY1Q", "01GD36ZV904SHBHNAYV6DX5QEF"}, updated.EmojiIDs)
|
||||||
suite.WithinDuration(time.Now(), updated.UpdatedAt, 5*time.Second)
|
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() {
|
func (suite *AccountTestSuite) TestInsertAccountWithDefaults() {
|
||||||
|
|
|
@ -67,12 +67,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var registerTables = []interface{}{
|
var registerTables = []interface{}{
|
||||||
|
>smodel.AccountToEmoji{},
|
||||||
>smodel.StatusToEmoji{},
|
>smodel.StatusToEmoji{},
|
||||||
>smodel.StatusToTag{},
|
>smodel.StatusToTag{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// bunDBService satisfies the DB interface
|
// DBService satisfies the DB interface
|
||||||
type bunDBService struct {
|
type DBService struct {
|
||||||
db.Account
|
db.Account
|
||||||
db.Admin
|
db.Admin
|
||||||
db.Basic
|
db.Basic
|
||||||
|
@ -89,6 +90,12 @@ type bunDBService struct {
|
||||||
conn *DBConn
|
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 {
|
func doMigration(ctx context.Context, db *bun.DB) error {
|
||||||
migrator := migrate.NewMigrator(db, migrations.Migrations)
|
migrator := migrate.NewMigrator(db, migrations.Migrations)
|
||||||
|
|
||||||
|
@ -177,7 +184,7 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
|
||||||
// Prepare domain block cache
|
// Prepare domain block cache
|
||||||
blockCache := cache.NewDomainBlockCache()
|
blockCache := cache.NewDomainBlockCache()
|
||||||
|
|
||||||
ps := &bunDBService{
|
ps := &DBService{
|
||||||
Account: accounts,
|
Account: accounts,
|
||||||
Admin: &adminDB{
|
Admin: &adminDB{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
|
@ -399,7 +406,7 @@ func tweakConnectionValues(sqldb *sql.DB) {
|
||||||
CONVERSION FUNCTIONS
|
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()
|
protocol := config.GetProtocol()
|
||||||
host := config.GetHost()
|
host := config.GetHost()
|
||||||
|
|
||||||
|
@ -408,7 +415,7 @@ func (ps *bunDBService) TagStringsToTags(ctx context.Context, tags []string, ori
|
||||||
tag := >smodel.Tag{}
|
tag := >smodel.Tag{}
|
||||||
// we can use selectorinsert here to create the new tag if it doesn't exist already
|
// 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
|
// 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 {
|
if err == sql.ErrNoRows {
|
||||||
// tag doesn't exist yet so populate it
|
// tag doesn't exist yet so populate it
|
||||||
newID, err := id.NewRandomULID()
|
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
|
// quickly fetch a remote account from the database or fail, and don't want to cause
|
||||||
// http requests to go flying around.
|
// http requests to go flying around.
|
||||||
SkipResolve bool
|
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,
|
// 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
|
skipResolve := params.SkipResolve
|
||||||
|
|
||||||
// this first step checks if we have the
|
// 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 {
|
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:
|
case params.RemoteAccountID != nil:
|
||||||
uri := params.RemoteAccountID
|
uri := params.RemoteAccountID
|
||||||
host := uri.Host
|
host := uri.Host
|
||||||
|
@ -163,7 +176,7 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar
|
||||||
params.RemoteAccountHost = params.RemoteAccountID.Host
|
params.RemoteAccountHost = params.RemoteAccountID.Host
|
||||||
// ... but we still need the username so we can do a finger for the accountDomain
|
// ... 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 {
|
if foundAccount != nil {
|
||||||
params.RemoteAccountUsername = foundAccount.Username
|
params.RemoteAccountUsername = foundAccount.Username
|
||||||
} else {
|
} else {
|
||||||
|
@ -201,9 +214,10 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar
|
||||||
// to save on remote calls, only webfinger if:
|
// to save on remote calls, only webfinger if:
|
||||||
// - we don't know the remote account ActivityPub ID yet OR
|
// - we don't know the remote account ActivityPub ID yet OR
|
||||||
// - we haven't found the account yet in some other way 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
|
// - we haven't webfingered the account for two days AND the account isn't an instance account
|
||||||
var fingered time.Time
|
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)
|
accountDomain, params.RemoteAccountID, err = d.fingerRemoteAccount(ctx, params.RequestingUsername, params.RemoteAccountUsername, params.RemoteAccountHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("GetRemoteAccount: error while fingering: %s", err)
|
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.LastWebfingeredAt = fingered
|
||||||
foundAccount.UpdatedAt = time.Now()
|
foundAccount.UpdatedAt = time.Now()
|
||||||
|
|
||||||
err = d.db.Put(ctx, foundAccount)
|
foundAccount, err = d.db.PutAccount(ctx, foundAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("GetRemoteAccount: error putting new account: %s", err)
|
err = fmt.Errorf("GetRemoteAccount: error putting new account: %s", err)
|
||||||
return
|
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
|
// 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) {
|
if !strings.EqualFold(foundAccount.Domain, accountDomain) {
|
||||||
|
accountDomainChanged = true
|
||||||
foundAccount.Domain = accountDomain
|
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
|
// 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
|
foundAccount.LastWebfingeredAt = fingered
|
||||||
}
|
}
|
||||||
|
|
||||||
if fieldsChanged || fingeredChanged || sharedInboxChanged {
|
if accountDomainChanged || sharedInboxChanged || fieldsChanged || fingeredChanged {
|
||||||
foundAccount.UpdatedAt = time.Now()
|
|
||||||
foundAccount, err = d.db.UpdateAccount(ctx, foundAccount)
|
foundAccount, err = d.db.UpdateAccount(ctx, foundAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetRemoteAccount: error updating remoteAccount: %s", err)
|
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)
|
return false, fmt.Errorf("populateAccountFields: domain %s is blocked", accountURI.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := d.transportController.NewTransportForUsername(ctx, requestingUsername)
|
var changed bool
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("populateAccountFields: error getting transport for user: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch the header and avatar
|
// fetch the header and avatar
|
||||||
changed, err := d.fetchRemoteAccountMedia(ctx, account, t, blocking)
|
if mediaChanged, err := d.fetchRemoteAccountMedia(ctx, account, requestingUsername, blocking); err != nil {
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("populateAccountFields: error fetching header/avi for account: %s", err)
|
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
|
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:
|
// 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.
|
// 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) {
|
func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsmodel.Account, requestingUsername string, blocking bool) (bool, error) {
|
||||||
changed := false
|
var (
|
||||||
|
changed bool
|
||||||
accountURI, err := url.Parse(targetAccount.URI)
|
t transport.Transport
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if targetAccount.AvatarRemoteURL != "" && (targetAccount.AvatarMediaAttachmentID == "") {
|
if targetAccount.AvatarRemoteURL != "" && (targetAccount.AvatarMediaAttachmentID == "") {
|
||||||
var processingMedia *media.ProcessingMedia
|
var processingMedia *media.ProcessingMedia
|
||||||
|
@ -479,6 +488,14 @@ func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsm
|
||||||
return changed, err
|
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) {
|
data := func(innerCtx context.Context) (io.Reader, int, error) {
|
||||||
return t.DereferenceMedia(innerCtx, avatarIRI)
|
return t.DereferenceMedia(innerCtx, avatarIRI)
|
||||||
}
|
}
|
||||||
|
@ -537,6 +554,14 @@ func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsm
|
||||||
return changed, err
|
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) {
|
data := func(innerCtx context.Context) (io.Reader, int, error) {
|
||||||
return t.DereferenceMedia(innerCtx, headerIRI)
|
return t.DereferenceMedia(innerCtx, headerIRI)
|
||||||
}
|
}
|
||||||
|
@ -580,6 +605,118 @@ func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsm
|
||||||
return changed, nil
|
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 {
|
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
|
// whatever happens, remove the in-process media from the map
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing"
|
"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -195,6 +196,205 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountWithUnknownUserURI() {
|
||||||
suite.Nil(fetchedAccount)
|
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) {
|
func TestAccountTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(AccountTestSuite))
|
suite.Run(t, new(AccountTestSuite))
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ type DereferencerStandardTestSuite struct {
|
||||||
testRemoteServices map[string]vocab.ActivityStreamsService
|
testRemoteServices map[string]vocab.ActivityStreamsService
|
||||||
testRemoteAttachments map[string]testrig.RemoteAttachmentFile
|
testRemoteAttachments map[string]testrig.RemoteAttachmentFile
|
||||||
testAccounts map[string]*gtsmodel.Account
|
testAccounts map[string]*gtsmodel.Account
|
||||||
|
testEmojis map[string]*gtsmodel.Emoji
|
||||||
|
|
||||||
dereferencer dereferencing.Dereferencer
|
dereferencer dereferencing.Dereferencer
|
||||||
}
|
}
|
||||||
|
@ -55,6 +56,7 @@ func (suite *DereferencerStandardTestSuite) SetupTest() {
|
||||||
suite.testRemoteGroups = testrig.NewTestFediGroups()
|
suite.testRemoteGroups = testrig.NewTestFediGroups()
|
||||||
suite.testRemoteServices = testrig.NewTestFediServices()
|
suite.testRemoteServices = testrig.NewTestFediServices()
|
||||||
suite.testRemoteAttachments = testrig.NewTestFediAttachments("../../../testrig/media")
|
suite.testRemoteAttachments = testrig.NewTestFediAttachments("../../../testrig/media")
|
||||||
|
suite.testEmojis = testrig.NewTestEmojis()
|
||||||
|
|
||||||
suite.db = testrig.NewTestDB()
|
suite.db = testrig.NewTestDB()
|
||||||
suite.storage = testrig.NewInMemoryStorage()
|
suite.storage = testrig.NewInMemoryStorage()
|
||||||
|
|
|
@ -24,6 +24,10 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"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"
|
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,3 +53,57 @@ func (d *deref) GetRemoteEmoji(ctx context.Context, requestingUsername string, r
|
||||||
|
|
||||||
return processingMedia, nil
|
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 {
|
func (d *deref) populateStatusEmojis(ctx context.Context, status *gtsmodel.Status, requestingUsername string) error {
|
||||||
// At this point we should know:
|
emojis, err := d.populateEmojis(ctx, status.Emojis, requestingUsername)
|
||||||
// * the AP uri of the emoji
|
if err != nil {
|
||||||
// * the domain of the emoji
|
return err
|
||||||
// * 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()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("populateStatusEmojis: 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("populateStatusEmojis: couldn't get remote emoji %s: %s", e.URI, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
emojiIDs := make([]string, 0, len(emojis))
|
||||||
|
for _, e := range emojis {
|
||||||
|
emojiIDs = append(emojiIDs, e.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
status.Emojis = emojis
|
||||||
status.EmojiIDs = emojiIDs
|
status.EmojiIDs = emojiIDs
|
||||||
return nil
|
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)
|
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
|
// no need to update local accounts
|
||||||
// in fact, if we do this will break the shit out of things so do NOT
|
// in fact, if we do this will break the shit out of things so do NOT
|
||||||
return nil
|
return nil
|
||||||
|
@ -136,13 +136,8 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
||||||
updatedAcct.ID = requestingAcct.ID
|
updatedAcct.ID = requestingAcct.ID
|
||||||
updatedAcct.Language = requestingAcct.Language
|
updatedAcct.Language = requestingAcct.Language
|
||||||
|
|
||||||
// do the update
|
// pass to the processor for further updating of eg., avatar/header, emojis
|
||||||
updatedAcct, err = f.db.UpdateAccount(ctx, updatedAcct)
|
// the actual db insert/update will take place a bit later
|
||||||
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
|
|
||||||
f.fedWorker.Queue(messages.FromFederator{
|
f.fedWorker.Queue(messages.FromFederator{
|
||||||
APObjectType: ap.ObjectProfile,
|
APObjectType: ap.ObjectProfile,
|
||||||
APActivityType: ap.ActivityUpdate,
|
APActivityType: ap.ActivityUpdate,
|
||||||
|
|
|
@ -41,6 +41,8 @@ type Account struct {
|
||||||
HeaderMediaAttachment *MediaAttachment `validate:"-" bun:"rel:belongs-to"` // MediaAttachment corresponding to headerMediaAttachmentID
|
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?
|
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.
|
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
|
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)
|
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
|
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
|
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.
|
// 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
|
// VerifiedAt is optional, to be used only if Value is a URL to a webpage that contains the
|
||||||
// username of the user.
|
// username of the user.
|
||||||
|
|
|
@ -259,6 +259,8 @@ selectStatusesLoop:
|
||||||
account.HeaderMediaAttachmentID = ""
|
account.HeaderMediaAttachmentID = ""
|
||||||
account.HeaderRemoteURL = ""
|
account.HeaderRemoteURL = ""
|
||||||
account.Reason = ""
|
account.Reason = ""
|
||||||
|
account.Emojis = []*gtsmodel.Emoji{}
|
||||||
|
account.EmojiIDs = []string{}
|
||||||
account.Fields = []gtsmodel.Field{}
|
account.Fields = []gtsmodel.Field{}
|
||||||
hideCollections := true
|
hideCollections := true
|
||||||
account.HideCollections = &hideCollections
|
account.HideCollections = &hideCollections
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
"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
|
account.Bot = form.Bot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updateEmojis bool
|
||||||
|
|
||||||
if form.DisplayName != nil {
|
if form.DisplayName != nil {
|
||||||
if err := validate.DisplayName(*form.DisplayName); err != nil {
|
if err := validate.DisplayName(*form.DisplayName); err != nil {
|
||||||
return nil, gtserror.NewErrorBadRequest(err)
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
account.DisplayName = text.SanitizePlaintext(*form.DisplayName)
|
account.DisplayName = text.SanitizePlaintext(*form.DisplayName)
|
||||||
|
updateEmojis = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Note != nil {
|
if form.Note != nil {
|
||||||
|
@ -69,6 +73,30 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
|
|
||||||
// Set updated HTML-ified note
|
// Set updated HTML-ified note
|
||||||
account.Note = 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 {
|
if form.Avatar != nil && form.Avatar.Size != 0 {
|
||||||
|
|
|
@ -369,10 +369,14 @@ func (p *processor) processUpdateAccountFromFederator(ctx context.Context, feder
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// further database updates occur inside getremoteaccount
|
||||||
if _, err := p.federator.GetRemoteAccount(ctx, dereferencing.GetRemoteAccountParams{
|
if _, err := p.federator.GetRemoteAccount(ctx, dereferencing.GetRemoteAccountParams{
|
||||||
RequestingUsername: federatorMsg.ReceivingAccount.Username,
|
RequestingUsername: federatorMsg.ReceivingAccount.Username,
|
||||||
RemoteAccountID: incomingAccountURL,
|
RemoteAccountID: incomingAccountURL,
|
||||||
Blocking: true,
|
RemoteAccountHost: incomingAccount.Domain,
|
||||||
|
RemoteAccountUsername: incomingAccount.Username,
|
||||||
|
PartialAccount: incomingAccount,
|
||||||
|
Blocking: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Errorf("error enriching updated account from federator: %s", err)
|
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
|
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
|
// TODO: fields aka attachment array
|
||||||
|
|
||||||
// note aka summary
|
// note aka summary
|
||||||
|
|
|
@ -473,6 +473,7 @@ type TypeUtilsTestSuite struct {
|
||||||
testAccounts map[string]*gtsmodel.Account
|
testAccounts map[string]*gtsmodel.Account
|
||||||
testStatuses map[string]*gtsmodel.Status
|
testStatuses map[string]*gtsmodel.Status
|
||||||
testPeople map[string]vocab.ActivityStreamsPerson
|
testPeople map[string]vocab.ActivityStreamsPerson
|
||||||
|
testEmojis map[string]*gtsmodel.Emoji
|
||||||
|
|
||||||
typeconverter typeutils.TypeConverter
|
typeconverter typeutils.TypeConverter
|
||||||
}
|
}
|
||||||
|
@ -485,6 +486,7 @@ func (suite *TypeUtilsTestSuite) SetupSuite() {
|
||||||
suite.testAccounts = testrig.NewTestAccounts()
|
suite.testAccounts = testrig.NewTestAccounts()
|
||||||
suite.testStatuses = testrig.NewTestStatuses()
|
suite.testStatuses = testrig.NewTestStatuses()
|
||||||
suite.testPeople = testrig.NewTestFediPeople()
|
suite.testPeople = testrig.NewTestFediPeople()
|
||||||
|
suite.testEmojis = testrig.NewTestEmojis()
|
||||||
suite.typeconverter = typeutils.NewConverter(suite.db)
|
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
|
// set the public key property on the Person
|
||||||
person.SetW3IDSecurityV1PublicKey(publicKeyProp)
|
person.SetW3IDSecurityV1PublicKey(publicKeyProp)
|
||||||
|
|
||||||
// tag
|
// tags
|
||||||
// TODO: Any tags used in the summary of this profile
|
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
|
// attachment
|
||||||
// Used for profile fields.
|
// Used for profile fields.
|
||||||
|
@ -477,11 +502,11 @@ func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.A
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, emoji := range emojis {
|
for _, emoji := range emojis {
|
||||||
asMention, err := c.EmojiToAS(ctx, emoji)
|
asEmoji, err := c.EmojiToAS(ctx, emoji)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("StatusToAS: error converting emoji to AS emoji: %s", err)
|
return nil, fmt.Errorf("StatusToAS: error converting emoji to AS emoji: %s", err)
|
||||||
}
|
}
|
||||||
tagProp.AppendTootEmoji(asMention)
|
tagProp.AppendTootEmoji(asEmoji)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tag -- hashtags
|
// tag -- hashtags
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"github.com/superseriousbusiness/activity/streams"
|
"github.com/superseriousbusiness/activity/streams"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,7 +35,8 @@ type InternalToASTestSuite struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *InternalToASTestSuite) TestAccountToAS() {
|
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)
|
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
@ -49,11 +51,33 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
|
||||||
// this is necessary because the order of multiple 'context' entries is not determinate
|
// this is necessary because the order of multiple 'context' entries is not determinate
|
||||||
trimmed := strings.Split(string(bytes), "\"discoverable\"")[1]
|
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() {
|
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"
|
sharedInbox := "http://localhost:8080/sharedInbox"
|
||||||
testAccount.SharedInboxURI = &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
|
// this is necessary because the order of multiple 'context' entries is not determinate
|
||||||
trimmed := strings.Split(string(bytes), "\"discoverable\"")[1]
|
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() {
|
func (suite *InternalToASTestSuite) TestOutboxToASCollection() {
|
||||||
|
|
|
@ -159,8 +159,29 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
||||||
fields = append(fields, mField)
|
fields = append(fields, mField)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// account emojis
|
||||||
emojis := []model.Emoji{}
|
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
|
var acct string
|
||||||
if a.Domain != "" {
|
if a.Domain != "" {
|
||||||
|
@ -194,7 +215,7 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
||||||
FollowingCount: followingCount,
|
FollowingCount: followingCount,
|
||||||
StatusesCount: statusesCount,
|
StatusesCount: statusesCount,
|
||||||
LastStatusAt: lastStatusAt,
|
LastStatusAt: lastStatusAt,
|
||||||
Emojis: emojis, // TODO: implement this
|
Emojis: emojis,
|
||||||
Fields: fields,
|
Fields: fields,
|
||||||
Suspended: suspended,
|
Suspended: suspended,
|
||||||
CustomCSS: a.CustomCSS,
|
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))
|
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() {
|
func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() {
|
||||||
testAccount := suite.testAccounts["local_account_1"] // take zork for this test
|
testAccount := suite.testAccounts["local_account_1"] // take zork for this test
|
||||||
apiAccount, err := suite.typeconverter.AccountToAPIAccountSensitive(context.Background(), testAccount)
|
apiAccount, err := suite.typeconverter.AccountToAPIAccountSensitive(context.Background(), testAccount)
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
|
|
||||||
var testModels = []interface{}{
|
var testModels = []interface{}{
|
||||||
>smodel.Account{},
|
>smodel.Account{},
|
||||||
|
>smodel.AccountToEmoji{},
|
||||||
>smodel.Application{},
|
>smodel.Application{},
|
||||||
>smodel.Block{},
|
>smodel.Block{},
|
||||||
>smodel.DomainBlock{},
|
>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(),
|
VisibleInPicker: TrueBool(),
|
||||||
CategoryID: "",
|
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",
|
Original: "rainbow-original.png",
|
||||||
Static: "rainbow-static.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
|
// RemoteAttachmentFile mimics a remote (federated) attachment
|
||||||
type RemoteAttachmentFile struct {
|
type RemoteAttachmentFile struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
|
@ -1968,6 +2010,16 @@ func NewTestFediAttachments(relativePath string) map[string]RemoteAttachmentFile
|
||||||
panic(err)
|
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{
|
return map[string]RemoteAttachmentFile{
|
||||||
"https://s3-us-west-2.amazonaws.com/plushcity/media_attachments/files/106/867/380/219/163/828/original/88e8758c5f011439.jpg": {
|
"https://s3-us-west-2.amazonaws.com/plushcity/media_attachments/files/106/867/380/219/163/828/original/88e8758c5f011439.jpg": {
|
||||||
Data: beeBytes,
|
Data: beeBytes,
|
||||||
|
@ -1985,6 +2037,14 @@ func NewTestFediAttachments(relativePath string) map[string]RemoteAttachmentFile
|
||||||
Data: peglinBytes,
|
Data: peglinBytes,
|
||||||
ContentType: "image/gif",
|
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
|
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
|
// NewAPNote returns a new activity streams note for the given parameters
|
||||||
func NewAPNote(
|
func NewAPNote(
|
||||||
noteID *url.URL,
|
noteID *url.URL,
|
||||||
|
|
|
@ -64,6 +64,7 @@ type MockHTTPClient struct {
|
||||||
testRemoteGroups map[string]vocab.ActivityStreamsGroup
|
testRemoteGroups map[string]vocab.ActivityStreamsGroup
|
||||||
testRemoteServices map[string]vocab.ActivityStreamsService
|
testRemoteServices map[string]vocab.ActivityStreamsService
|
||||||
testRemoteAttachments map[string]RemoteAttachmentFile
|
testRemoteAttachments map[string]RemoteAttachmentFile
|
||||||
|
testRemoteEmojis map[string]vocab.TootEmoji
|
||||||
|
|
||||||
SentMessages sync.Map
|
SentMessages sync.Map
|
||||||
}
|
}
|
||||||
|
@ -90,6 +91,7 @@ func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error), relat
|
||||||
mockHTTPClient.testRemoteGroups = NewTestFediGroups()
|
mockHTTPClient.testRemoteGroups = NewTestFediGroups()
|
||||||
mockHTTPClient.testRemoteServices = NewTestFediServices()
|
mockHTTPClient.testRemoteServices = NewTestFediServices()
|
||||||
mockHTTPClient.testRemoteAttachments = NewTestFediAttachments(relativeMediaPath)
|
mockHTTPClient.testRemoteAttachments = NewTestFediAttachments(relativeMediaPath)
|
||||||
|
mockHTTPClient.testRemoteEmojis = NewTestFediEmojis()
|
||||||
|
|
||||||
mockHTTPClient.do = func(req *http.Request) (*http.Response, error) {
|
mockHTTPClient.do = func(req *http.Request) (*http.Response, error) {
|
||||||
responseCode := http.StatusNotFound
|
responseCode := http.StatusNotFound
|
||||||
|
@ -173,6 +175,19 @@ func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error), relat
|
||||||
responseBytes = serviceJSON
|
responseBytes = serviceJSON
|
||||||
responseContentType = applicationActivityJSON
|
responseContentType = applicationActivityJSON
|
||||||
responseContentLength = len(serviceJSON)
|
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 {
|
} else if attachment, ok := mockHTTPClient.testRemoteAttachments[req.URL.String()]; ok {
|
||||||
responseCode = http.StatusOK
|
responseCode = http.StatusOK
|
||||||
responseBytes = attachment.Data
|
responseBytes = attachment.Data
|
||||||
|
|
|
@ -12,12 +12,12 @@
|
||||||
<div class="basic">
|
<div class="basic">
|
||||||
<div id="profile-basic-filler2"></div>
|
<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>
|
<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 class="username"><span>@{{.account.Username}}</span><span>@{{.instance.AccountDomain}}</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="detailed">
|
<div class="detailed">
|
||||||
<div class="bio">
|
<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>
|
</div>
|
||||||
<div class="accountstats">
|
<div class="accountstats">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="contentgrid">
|
<div class="contentgrid">
|
||||||
<a href="{{.Account.URL}}" class="avatar"><img src="{{.Account.Avatar}}" alt=""></a>
|
<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>
|
<a href="{{.Account.URL}}" class="username">@{{.Account.Acct}}</a>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
{{if .SpoilerText}}
|
{{if .SpoilerText}}
|
||||||
|
|
Loading…
Reference in a new issue