[chore/frontend] Opengraph: for statuses with media, omit account and instance avatars (#4638)

# Description

This produces a better experience for consumers that can handle multiple OpenGraph attachments, like Discord (see #4631 for an example).

_This is among the first Go code I've written, so please tell me what I've done wrong! The testing is also a bit light, but the existing testing was light as well._

closes #4631

## Checklist

Please put an x inside each checkbox to indicate that you've read and followed it: `[ ]` -> `[x]`

If this is a documentation change, only the first two checkboxes must be filled (you can delete the others if you want).

- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have not used so-called 'AI' to create the proposed changes.
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [x] I/we have made any necessary changes to documentation. _(none)_
- [x] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.\* _Some bundb tests timed out on my machine, but they shouldn't be affected._
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.\* _There are some existing lint failures but they may be because I didn't install the exact same version of Go._

Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4638
Co-authored-by: Jordan Rose <jrose@belkadan.com>
Co-committed-by: Jordan Rose <jrose@belkadan.com>
This commit is contained in:
Jordan Rose
2026-01-26 13:08:58 +01:00
committed by tobi
parent 9afd66f653
commit 513a4e5507
2 changed files with 232 additions and 8 deletions
+9 -8
View File
@@ -180,8 +180,8 @@ func (o *OGMeta) WithStatus(status *apimodel.WebStatus) *OGMeta {
o.Description = o.Title
}
// Prepend account image.
o.prependMedia(ogImgForAcct(status.Account))
// Gather ogMedias for this status.
ogMedias := []OGMedia{}
if l := len(status.MediaAttachments); l != 0 && !status.Sensitive {
@@ -202,10 +202,6 @@ func (o *OGMeta) WithStatus(status *apimodel.WebStatus) *OGMeta {
Alt: desc,
}
// Gather ogMedias for
// this attachment.
ogMedias := []OGMedia{}
// Add further tags
// depending on type.
switch a.Type {
@@ -252,18 +248,23 @@ func (o *OGMeta) WithStatus(status *apimodel.WebStatus) *OGMeta {
)
}
// Prepend gathered entries.
// Replace generic entries with gathered ones.
//
// This will cause the full-size
// entry to appear before its
// thumbnail entry (if set).
o.prependMedia(ogMedias...)
o.Media = ogMedias
// Done!
break
}
}
if len(ogMedias) == 0 {
// Prepend account image to default media.
o.prependMedia(ogImgForAcct(status.Account))
}
o.ArticlePublisher = status.Account.URL
o.ArticleAuthor = status.Account.URL
o.ArticlePublishedTime = status.CreatedAt
+223
View File
@@ -21,6 +21,9 @@ import (
"testing"
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/typeutils"
"code.superseriousbusiness.org/gotosocial/internal/util"
"github.com/stretchr/testify/suite"
)
@@ -138,6 +141,226 @@ func (suite *OpenGraphTestSuite) TestWithAccountNoNote() {
}, *accountMeta)
}
func (suite *OpenGraphTestSuite) TestWithStatus() {
baseMeta := OGBase(&apimodel.InstanceV1{
AccountDomain: "example.org",
Languages: []string{"en"},
Thumbnail: "https://example.org/instance-avatar.webp",
ThumbnailType: "image/webp",
})
acct := &apimodel.Account{
Acct: "example_account",
DisplayName: "example person!!",
URL: "https://example.org/@example_account",
Note: "", // <- empty
Username: "example_account",
Avatar: "https://example.org/avatar.jpg",
}
apiStatus := &apimodel.Status{
ID: "12345",
CreatedAt: "2025-01-18T00:00:00+00:00",
EditedAt: util.Ptr("2025-01-18T11:00:00+00:00"),
Sensitive: false,
SpoilerText: "",
Visibility: typeutils.VisToAPIVis(gtsmodel.VisibilityPublic),
LocalOnly: false,
Language: util.Ptr("en"),
URI: "https://example.org/statuses/12345",
URL: "https://example.org/@example_account/12345",
Content: "<b>test status</b>",
Account: acct,
MediaAttachments: []*apimodel.Attachment{},
Text: "**test status**",
ContentType: apimodel.StatusContentTypeMarkdown,
}
status := &apimodel.WebStatus{
Status: apiStatus,
SpoilerContent: "", // <- empty
Account: &apimodel.WebAccount{
Account: acct,
AvatarAttachment: nil,
HeaderAttachment: nil,
WebLayout: gtsmodel.WebLayoutMicroblog.String(),
},
}
statusMeta := baseMeta.WithStatus(status)
suite.EqualValues(OGMeta{
Title: "Post by example person!!, @example_account@example.org",
Type: "article",
Locale: "en",
URL: "https://example.org/@example_account/12345",
SiteName: "example.org",
Description: "**test status**",
Media: []OGMedia{
{
OGType: "image",
Alt: "Avatar for example_account",
URL: "https://example.org/avatar.jpg",
},
{
// Instance avatar.
OGType: "image",
URL: "https://example.org/instance-avatar.webp",
MIMEType: "image/webp",
},
},
ArticlePublisher: "https://example.org/@example_account",
ArticleAuthor: "https://example.org/@example_account",
ArticleModifiedTime: "2025-01-18T11:00:00+00:00",
ArticlePublishedTime: "2025-01-18T00:00:00+00:00",
ProfileUsername: "",
}, *statusMeta)
}
func (suite *OpenGraphTestSuite) TestWithStatusWithImage() {
baseMeta := OGBase(&apimodel.InstanceV1{
AccountDomain: "example.org",
Languages: []string{"en"},
Thumbnail: "https://example.org/instance-avatar.webp",
ThumbnailType: "image/webp",
})
acct := &apimodel.Account{
Acct: "example_account",
DisplayName: "example person!!",
URL: "https://example.org/@example_account",
Note: "", // <- empty
Username: "example_account",
Avatar: "https://example.org/avatar.jpg",
}
imageAttachment := &apimodel.Attachment{
ID: "00IMAGE00",
Type: "image",
URL: util.Ptr("https://example.org/@example_account/12345/example.png"),
TextURL: util.Ptr("https://example.org/@example_account/12345/example.png"),
PreviewURL: util.Ptr("https://example.org/@example_account/12345/small/example.png"),
RemoteURL: nil,
PreviewRemoteURL: nil,
Meta: &apimodel.MediaMeta{
Original: apimodel.MediaDimensions{
Width: 1920,
Height: 1080,
Size: "1920x1080",
Aspect: 1920.0 / 1080,
},
Small: apimodel.MediaDimensions{
Width: 320,
Height: 240,
Size: "320x240",
Aspect: 320.0 / 240,
},
Focus: nil,
},
Description: util.Ptr("an example image"),
Blurhash: util.Ptr("LKE3VIw}0KD%a2o{M|t7NFWps:t7"), // <- from testmodels
}
anotherImageAttachment := &apimodel.Attachment{
ID: "00IMAGE11",
Type: "image",
URL: util.Ptr("https://example.org/@example_account/12345/example2.png"),
TextURL: util.Ptr("https://example.org/@example_account/12345/example2.png"),
PreviewURL: util.Ptr("https://example.org/@example_account/12345/small/example2.png"),
RemoteURL: nil,
PreviewRemoteURL: nil,
Meta: &apimodel.MediaMeta{
Original: apimodel.MediaDimensions{
Width: 1000,
Height: 1000,
Size: "1000x1000",
Aspect: 1,
},
Small: apimodel.MediaDimensions{
Width: 200,
Height: 200,
Size: "200x200",
Aspect: 1,
},
Focus: nil,
},
Description: util.Ptr("another example image"),
Blurhash: util.Ptr("LNABP8o#Dge,S6M}axxVEQjYxWbH"), // <- from testmodels
}
apiStatus := &apimodel.Status{
ID: "12345",
CreatedAt: "2025-01-18T00:00:00+00:00",
EditedAt: util.Ptr("2025-01-18T11:00:00+00:00"),
Sensitive: false,
SpoilerText: "",
Visibility: typeutils.VisToAPIVis(gtsmodel.VisibilityPublic),
LocalOnly: false,
Language: util.Ptr("en"),
URI: "https://example.org/statuses/12345",
URL: "https://example.org/@example_account/12345",
Content: "<b>test status</b>",
Account: acct,
MediaAttachments: []*apimodel.Attachment{imageAttachment, anotherImageAttachment},
Text: "**test status**",
ContentType: apimodel.StatusContentTypeMarkdown,
}
webAttachment := &apimodel.WebAttachment{
Attachment: imageAttachment,
Sensitive: false,
MIMEType: "image/png",
PreviewMIMEType: "image/png",
ParentStatusLink: "https://example.org/@example_account/12345",
}
anotherWebAttachment := &apimodel.WebAttachment{
Attachment: anotherImageAttachment,
Sensitive: false,
MIMEType: "image/png",
PreviewMIMEType: "image/png",
ParentStatusLink: "https://example.org/@example_account/12345",
}
status := &apimodel.WebStatus{
Status: apiStatus,
SpoilerContent: "", // <- empty
MediaAttachments: []*apimodel.WebAttachment{webAttachment, anotherWebAttachment},
Account: &apimodel.WebAccount{
Account: acct,
AvatarAttachment: nil,
HeaderAttachment: nil,
WebLayout: gtsmodel.WebLayoutMicroblog.String(),
},
}
statusMeta := baseMeta.WithStatus(status)
suite.EqualValues(OGMeta{
Title: "Post by example person!!, @example_account@example.org",
Type: "article",
Locale: "en",
URL: "https://example.org/@example_account/12345",
SiteName: "example.org",
Description: "**test status**",
Media: []OGMedia{
{
OGType: "image",
Alt: "an example image",
URL: "https://example.org/@example_account/12345/example.png",
MIMEType: "image/png",
Width: "1920",
Height: "1080",
},
},
ArticlePublisher: "https://example.org/@example_account",
ArticleAuthor: "https://example.org/@example_account",
ArticleModifiedTime: "2025-01-18T11:00:00+00:00",
ArticlePublishedTime: "2025-01-18T00:00:00+00:00",
ProfileUsername: "",
}, *statusMeta)
}
func TestOpenGraphTestSuite(t *testing.T) {
suite.Run(t, &OpenGraphTestSuite{})
}