diff --git a/internal/api/client/statuses/statusedit_test.go b/internal/api/client/statuses/statusedit_test.go new file mode 100644 index 000000000..43b283d6d --- /dev/null +++ b/internal/api/client/statuses/statusedit_test.go @@ -0,0 +1,32 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 . + +package statuses_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type StatusEditTestSuite struct { + StatusStandardTestSuite +} + +func TestStatusEditTestSuite(t *testing.T) { + suite.Run(t, new(StatusEditTestSuite)) +} diff --git a/internal/federation/dereferencing/dereferencer.go b/internal/federation/dereferencing/dereferencer.go index 3bff0d1a2..5e7b2b9c0 100644 --- a/internal/federation/dereferencing/dereferencer.go +++ b/internal/federation/dereferencing/dereferencer.go @@ -66,7 +66,7 @@ var ( // causing loads of dereferencing calls. Fresh = util.Ptr(FreshnessWindow(5 * time.Minute)) - // 10 seconds. + // 5 seconds. // // Freshest is useful when you want an // immediately up to date model of something @@ -74,7 +74,7 @@ var ( // // Be careful using this one; it can cause // lots of unnecessary traffic if used unwisely. - Freshest = util.Ptr(FreshnessWindow(10 * time.Second)) + Freshest = util.Ptr(FreshnessWindow(5 * time.Second)) ) // Dereferencer wraps logic and functionality for doing dereferencing diff --git a/internal/processing/status/edit_test.go b/internal/processing/status/edit_test.go new file mode 100644 index 000000000..cbd76e490 --- /dev/null +++ b/internal/processing/status/edit_test.go @@ -0,0 +1,36 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 . + +package status_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type StatusEditTestSuite struct { + StatusStandardTestSuite +} + +func (suite *StatusEditTestSuite) TestEdit() { + suite.T().Fatal("TODO") +} + +func TestStatusEditTestSuite(t *testing.T) { + suite.Run(t, new(StatusEditTestSuite)) +} diff --git a/internal/processing/workers/fromfediapi.go b/internal/processing/workers/fromfediapi.go index 0d6ec1836..096e285f6 100644 --- a/internal/processing/workers/fromfediapi.go +++ b/internal/processing/workers/fromfediapi.go @@ -762,7 +762,7 @@ func (p *fediAPI) UpdateAccount(ctx context.Context, fMsg *messages.FromFediAPI) account, apubAcc, - // Force refresh within 10s window. + // Force refresh within 5s window. // // Missing account updates could be // detrimental to federation if they @@ -917,17 +917,25 @@ func (p *fediAPI) UpdateStatus(ctx context.Context, fMsg *messages.FromFediAPI) return gtserror.Newf("cannot cast %T -> *gtsmodel.Status", fMsg.GTSModel) } + var freshness *dereferencing.FreshnessWindow + // Cast the updated ActivityPub statusable object . apStatus, _ := fMsg.APObject.(ap.Statusable) + if apStatus != nil { + // If an AP object was provided, we + // allow very fast refreshes that likely + // indicate a status edit after post. + freshness = dereferencing.Freshest + } + // Fetch up-to-date attach status attachments, etc. status, _, err := p.federate.RefreshStatus( ctx, fMsg.Receiving.Username, existing, apStatus, - // Force refresh within 5min window. - dereferencing.Fresh, + freshness, ) if err != nil { log.Errorf(ctx, "error refreshing status: %v", err) diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 222b73461..d50349061 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -1494,10 +1494,37 @@ func (c *Converter) StatusToAPIEdits(ctx context.Context, status *gtsmodel.Statu return nil, gtserror.Newf("error converting emojis: %w", err) } - // Iterate through status edits, starting at newest (highest index). - apiEdits := make([]*apimodel.StatusEdit, 0, len(status.Edits)) - for i := len(status.Edits) - 1; i >= 0; i-- { - edit := status.Edits[i] + var votes []int + var options []string + + if status.Poll != nil { + // Extract status poll options. + options = status.Poll.Options + + // Show votes only if closed / allowed. + if !status.Poll.ClosedAt.IsZero() || + !*status.Poll.HideCounts { + votes = status.Poll.Votes + } + } + + // Append status itself to final slot in the edits + // so we can add its revision using the below loop. + edits := append(status.Edits, >smodel.StatusEdit{ //nolint:gocritic + Content: status.Content, + ContentWarning: status.ContentWarning, + Sensitive: status.Sensitive, + PollOptions: options, + PollVotes: votes, + AttachmentIDs: status.AttachmentIDs, + AttachmentDescriptions: nil, // no change from current + CreatedAt: status.UpdatedAt, + }) + + // Iterate through status edits, starting at newest. + apiEdits := make([]*apimodel.StatusEdit, 0, len(edits)) + for i := len(edits) - 1; i >= 0; i-- { + edit := edits[i] // Iterate through edit attachment IDs, getting model from 'media' lookup. apiAttachments := make([]*apimodel.Attachment, 0, len(edit.AttachmentIDs)) @@ -1564,7 +1591,7 @@ func (c *Converter) StatusToAPIEdits(ctx context.Context, status *gtsmodel.Statu Account: apiAccount, Poll: apiPoll, MediaAttachments: apiAttachments, - Emojis: apiEmojis, + Emojis: apiEmojis, // todo }) } diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go index 0ec9ea05f..558386ad6 100644 --- a/internal/typeutils/internaltofrontend_test.go +++ b/internal/typeutils/internaltofrontend_test.go @@ -3737,6 +3737,133 @@ func (suite *InternalToFrontendTestSuite) TestConversationToAPI() { }`, string(b)) } +func (suite *InternalToFrontendTestSuite) TestStatusToAPIEdits() { + ctx, cncl := context.WithCancel(context.Background()) + defer cncl() + + statusID := suite.testStatuses["local_account_1_status_9"].ID + + status, err := suite.state.DB.GetStatusByID(ctx, statusID) + suite.NoError(err) + + apiEdits, err := suite.typeconverter.StatusToAPIEdits(ctx, status) + suite.NoError(err) + + b, err := json.MarshalIndent(apiEdits, "", " ") + suite.NoError(err) + + suite.Equal(`[ + { + "content": "\u003cp\u003ethis is the latest revision of the status, with a content-warning\u003c/p\u003e", + "spoiler_text": "edited status", + "sensitive": false, + "created_at": "2024-11-01T09:02:00.000Z", + "account": { + "id": "01F8MH1H7YV1Z7D2C8K2730QBF", + "username": "the_mighty_zork", + "acct": "the_mighty_zork", + "display_name": "original zork (he/they)", + "locked": false, + "discoverable": true, + "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.jpg", + "avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.webp", + "avatar_description": "a green goblin looking nasty", + "avatar_media_id": "01F8MH58A357CV5K7R7TJMSH6S", + "header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg", + "header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.webp", + "header_description": "A very old-school screenshot of the original team fortress mod for quake", + "header_media_id": "01PFPMWK2FF0D9WMHEJHR07C3Q", + "followers_count": 2, + "following_count": 2, + "statuses_count": 9, + "last_status_at": "2024-11-01", + "emojis": [], + "fields": [], + "enable_rss": true + }, + "poll": null, + "media_attachments": [], + "emojis": [] + }, + { + "content": "\u003cp\u003ethis is the first status edit! now with content-warning\u003c/p\u003e", + "spoiler_text": "edited status", + "sensitive": false, + "created_at": "2024-11-01T09:01:00.000Z", + "account": { + "id": "01F8MH1H7YV1Z7D2C8K2730QBF", + "username": "the_mighty_zork", + "acct": "the_mighty_zork", + "display_name": "original zork (he/they)", + "locked": false, + "discoverable": true, + "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.jpg", + "avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.webp", + "avatar_description": "a green goblin looking nasty", + "avatar_media_id": "01F8MH58A357CV5K7R7TJMSH6S", + "header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg", + "header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.webp", + "header_description": "A very old-school screenshot of the original team fortress mod for quake", + "header_media_id": "01PFPMWK2FF0D9WMHEJHR07C3Q", + "followers_count": 2, + "following_count": 2, + "statuses_count": 9, + "last_status_at": "2024-11-01", + "emojis": [], + "fields": [], + "enable_rss": true + }, + "poll": null, + "media_attachments": [], + "emojis": [] + }, + { + "content": "\u003cp\u003ethis is the original status\u003c/p\u003e", + "spoiler_text": "", + "sensitive": false, + "created_at": "2024-11-01T09:00:00.000Z", + "account": { + "id": "01F8MH1H7YV1Z7D2C8K2730QBF", + "username": "the_mighty_zork", + "acct": "the_mighty_zork", + "display_name": "original zork (he/they)", + "locked": false, + "discoverable": true, + "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.jpg", + "avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.webp", + "avatar_description": "a green goblin looking nasty", + "avatar_media_id": "01F8MH58A357CV5K7R7TJMSH6S", + "header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg", + "header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.webp", + "header_description": "A very old-school screenshot of the original team fortress mod for quake", + "header_media_id": "01PFPMWK2FF0D9WMHEJHR07C3Q", + "followers_count": 2, + "following_count": 2, + "statuses_count": 9, + "last_status_at": "2024-11-01", + "emojis": [], + "fields": [], + "enable_rss": true + }, + "poll": null, + "media_attachments": [], + "emojis": [] + } +]`, string(b)) +} + func TestInternalToFrontendTestSuite(t *testing.T) { suite.Run(t, new(InternalToFrontendTestSuite)) }