Merge branch 'main' into add_gallery_web_rendering_mode

This commit is contained in:
tobi 2025-03-24 13:29:25 +01:00
commit 5822d451c7
214 changed files with 29471 additions and 4720 deletions

View file

@ -98,10 +98,17 @@ const (
// that we don't *yet* know what type of Object something is.
ObjectUnknown = "Unknown"
// Extensions and unofficial additions.
/* Extensions and unofficial additions */
/* GtS stuff */
ObjectLikeApproval = "LikeApproval"
ObjectReplyApproval = "ReplyApproval"
ObjectAnnounceApproval = "AnnounceApproval"
/* Funkwhale stuff */
ObjectAlbum = "Album"
)
// isActivity returns whether AS type name is of an Activity (NOT IntransitiveActivity).

View file

@ -1078,7 +1078,14 @@ func ExtractInteractionPolicy(
statusable Statusable,
owner *gtsmodel.Account,
) *gtsmodel.InteractionPolicy {
policyProp := statusable.GetGoToSocialInteractionPolicy()
ipa, ok := statusable.(InteractionPolicyAware)
if !ok {
// Not a type with interaction
// policy properties settable.
return nil
}
policyProp := ipa.GetGoToSocialInteractionPolicy()
if policyProp == nil || policyProp.Len() != 1 {
return nil
}

View file

@ -76,7 +76,8 @@ func IsStatusable(typeName string) bool {
ObjectEvent,
ObjectPlace,
ObjectProfile,
ActivityQuestion:
ActivityQuestion,
ObjectAlbum:
return true
default:
return false
@ -226,11 +227,13 @@ type Statusable interface {
WithTo
WithCc
WithSensitive
WithConversation
WithContent
WithAttachment
WithTag
WithReplies
}
type InteractionPolicyAware interface {
WithInteractionPolicy
WithApprovedBy
}
@ -589,10 +592,6 @@ type WithSensitive interface {
SetActivityStreamsSensitive(vocab.ActivityStreamsSensitiveProperty)
}
// WithConversation ...
type WithConversation interface { // TODO
}
// WithContent represents an activity with ActivityStreamsContentProperty
type WithContent interface {
GetActivityStreamsContent() vocab.ActivityStreamsContentProperty

View file

@ -80,6 +80,56 @@ func (suite *ResolveTestSuite) TestResolveNonAPJSONAsAccountable() {
suite.Nil(accountable)
}
func (suite *ResolveTestSuite) TestResolveBandwagonAlbumAsStatusable() {
b := []byte(`{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"discoverable": "toot:discoverable",
"indexable": "toot:indexable",
"toot": "https://joinmastodon.org/ns#"
},
"https://funkwhale.audio/ns"
],
"artists": [
{
"id": "https://bandwagon.fm/@67a0a0808121f77ed3466870",
"name": "Luka Prinčič",
"type": "Artist"
}
],
"attachment": [
{
"mediaType": "image/webp",
"name": "image",
"type": "Document",
"url": "https://bandwagon.fm/67a0a219f050061c8b4ce427/attachments/67a0a21bf050061c8b4ce429"
}
],
"attributedTo": "https://bandwagon.fm/@67a0a0808121f77ed3466870",
"content": "... a transgenre mutation, a fluid entity, jagged pop, electro-funk, techno-cabaret, a schlager, and soft alternative, queer to the core, satire and tragedy, sharp and fun indulgence for the dance of bodies and brains, activism and hedonism, which would all like to steal your attention.\r\n\r\nDRAGX̶FUNK is pronounced /dɹæɡɑːfʌŋk/.\r\n\r\n---\r\n\r\n## Buy digital\r\n💳 [Stripe](https://buy.stripe.com/6oE8x52iG1Kq5pKeV3)\r\n\r\n---\r\n\r\n## Buy dl/merch\r\n🎵 [Bandcamp](https://lukaprincic.bandcamp.com/album/dragx-funk) \r\n\r\n---\r\n\r\n## More:\r\n🌐 [prin.lu](https://prin.lu/music/241205_dragx-funk/) \r\n👉 [kamizdat.si](https://kamizdat.si/releases/dragx-funk-2/)\r\n",
"context": "https://bandwagon.fm/67a0a219f050061c8b4ce427",
"id": "https://bandwagon.fm/67a0a219f050061c8b4ce427",
"library": "https://bandwagon.fm/67a0a219f050061c8b4ce427/pub/children",
"license": "CC-BY-NC-SA",
"name": "DRAGX̶FUNK",
"published": "2025-03-17T11:40:53Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"tracks": "https://bandwagon.fm/67a0a219f050061c8b4ce427/pub/children",
"type": "Album",
"url": "https://bandwagon.fm/67a0a219f050061c8b4ce427"
}`)
statusable, err := ap.ResolveStatusable(
context.Background(), io.NopCloser(bytes.NewReader(b)),
)
suite.NoError(err)
suite.NotNil(statusable)
}
func TestResolveTestSuite(t *testing.T) {
suite.Run(t, &ResolveTestSuite{})
}

View file

@ -153,7 +153,9 @@ func serializeStatusable(t vocab.Type, includeContext bool) (map[string]interfac
NormalizeOutgoingAttachmentProp(statusable, data)
NormalizeOutgoingContentProp(statusable, data)
NormalizeOutgoingInteractionPolicyProp(statusable, data)
if ipa, ok := statusable.(InteractionPolicyAware); ok {
NormalizeOutgoingInteractionPolicyProp(ipa, data)
}
return data, nil
}

View file

@ -144,8 +144,12 @@ func (c *Caches) Start() error {
func (c *Caches) Stop() {
log.Infof(nil, "stop: %p", c)
_ = c.Webfinger.Stop()
_ = c.StatusesFilterableFields.Stop()
if c.Webfinger != nil {
_ = c.Webfinger.Stop()
}
if c.StatusesFilterableFields != nil {
_ = c.StatusesFilterableFields.Stop()
}
}
// Sweep will sweep all the available caches to ensure none

View file

@ -19,6 +19,7 @@ package subscriptions
import (
"bufio"
"cmp"
"context"
"encoding/csv"
"encoding/json"
@ -869,10 +870,13 @@ func (s *Subscriptions) adoptPerm(
perm.SetCreatedByAccount(permSub.CreatedByAccount)
// Set new metadata on the perm.
perm.SetObfuscate(obfuscate)
perm.SetPrivateComment(privateComment)
perm.SetPublicComment(publicComment)
// Avoid trying to blat nil into the db directly by
// defaulting to false if not set on wanted perm.
perm.SetObfuscate(cmp.Or(obfuscate, util.Ptr(false)))
// Update the perm in the db.
var err error
switch p := perm.(type) {

View file

@ -24,6 +24,7 @@ import (
"time"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/subscriptions"
@ -814,6 +815,141 @@ func (suite *SubscriptionsTestSuite) TestAdoption() {
suite.Equal(testSubscription.ID, existingBlock3.SubscriptionID)
}
func (suite *SubscriptionsTestSuite) TestDomainAllowsAndBlocks() {
var (
ctx = context.Background()
testStructs = testrig.SetupTestStructs(rMediaPath, rTemplatePath)
testAccount = suite.testAccounts["admin_account"]
subscriptions = subscriptions.New(
testStructs.State,
testStructs.TransportController,
testStructs.TypeConverter,
)
// Create a subscription for a CSV list of goodies.
// This one adopts orphans.
testAllowSubscription = &gtsmodel.DomainPermissionSubscription{
ID: "01JGE681TQSBPAV59GZXPKE62H",
Priority: 255,
Title: "goodies!",
PermissionType: gtsmodel.DomainPermissionAllow,
AsDraft: util.Ptr(false),
AdoptOrphans: util.Ptr(true),
CreatedByAccountID: testAccount.ID,
CreatedByAccount: testAccount,
URI: "https://lists.example.org/goodies",
ContentType: gtsmodel.DomainPermSubContentTypePlain,
}
existingAllow = &gtsmodel.DomainAllow{
ID: "01JHX2V5WN250TKB6FQ1M3QE1H",
Domain: "people.we.like.com",
CreatedByAccount: testAccount,
CreatedByAccountID: testAccount.ID,
}
testBlockSubscription = &gtsmodel.DomainPermissionSubscription{
ID: "01JPMVY19TKZND838Z7Y6S4EG8",
Priority: 255,
Title: "baddies!",
PermissionType: gtsmodel.DomainPermissionBlock,
AsDraft: util.Ptr(false),
AdoptOrphans: util.Ptr(false),
CreatedByAccountID: testAccount.ID,
CreatedByAccount: testAccount,
URI: "https://lists.example.org/baddies.csv",
ContentType: gtsmodel.DomainPermSubContentTypeCSV,
}
)
defer testrig.TearDownTestStructs(testStructs)
// Store test subscriptions.
if err := testStructs.State.DB.PutDomainPermissionSubscription(
ctx, testAllowSubscription,
); err != nil {
suite.FailNow(err.Error())
}
if err := testStructs.State.DB.PutDomainPermissionSubscription(
ctx, testBlockSubscription,
); err != nil {
suite.FailNow(err.Error())
}
// Store existing allow.
if err := testStructs.State.DB.CreateDomainAllow(ctx, existingAllow); err != nil {
suite.FailNow(err.Error())
}
// Put the instance in allowlist mode.
config.SetInstanceFederationMode("allowlist")
// Fetch + process subscribed perms in order.
var order [2]gtsmodel.DomainPermissionType
if config.GetInstanceFederationMode() == config.InstanceFederationModeBlocklist {
order = [2]gtsmodel.DomainPermissionType{
gtsmodel.DomainPermissionAllow,
gtsmodel.DomainPermissionBlock,
}
} else {
order = [2]gtsmodel.DomainPermissionType{
gtsmodel.DomainPermissionBlock,
gtsmodel.DomainPermissionAllow,
}
}
for _, permType := range order {
subscriptions.ProcessDomainPermissionSubscriptions(ctx, permType)
}
// We should now have allows for each
// domain on the subscribed allow list.
for _, domain := range []string{
"people.we.like.com",
"goodeggs.org",
"allowthesefolks.church",
} {
var (
perm gtsmodel.DomainPermission
err error
)
if !testrig.WaitFor(func() bool {
perm, err = testStructs.State.DB.GetDomainAllow(ctx, domain)
return err == nil
}) {
suite.FailNowf("", "timed out waiting for domain %s", domain)
}
suite.Equal(testAllowSubscription.ID, perm.GetSubscriptionID())
}
// And blocks for for each domain
// on the subscribed block list.
for _, domain := range []string{
"bumfaces.net",
"peepee.poopoo",
"nothanks.com",
} {
var (
perm gtsmodel.DomainPermission
err error
)
if !testrig.WaitFor(func() bool {
perm, err = testStructs.State.DB.GetDomainBlock(ctx, domain)
return err == nil
}) {
suite.FailNowf("", "timed out waiting for domain %s", domain)
}
suite.Equal(testBlockSubscription.ID, perm.GetSubscriptionID())
}
var err error
existingAllow, err = testStructs.State.DB.GetDomainAllow(ctx, "people.we.like.com")
if err != nil {
suite.FailNow(err.Error())
}
suite.Equal(existingAllow.SubscriptionID, testAllowSubscription.ID)
}
func TestSubscriptionTestSuite(t *testing.T) {
suite.Run(t, new(SubscriptionsTestSuite))
}

View file

@ -434,9 +434,10 @@ func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
return nil, gtserror.SetMalformed(err)
}
// Status was sent to us or dereffed
// by us so it must be federated.
// Status was sent to us or dereffed by
// us so it must be federated and not local.
status.Federated = util.Ptr(true)
status.Local = util.Ptr(false)
// Derive interaction policy for this status.
status.InteractionPolicy = ap.ExtractInteractionPolicy(
@ -446,9 +447,11 @@ func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
// Set approvedByURI if present,
// for later dereferencing.
approvedByURI := ap.GetApprovedBy(statusable)
if approvedByURI != nil {
status.ApprovedByURI = approvedByURI.String()
if ipa, ok := statusable.(ap.InteractionPolicyAware); ok {
approvedByURI := ap.GetApprovedBy(ipa)
if approvedByURI != nil {
status.ApprovedByURI = approvedByURI.String()
}
}
// Assume not pending approval; this may

View file

@ -21,7 +21,6 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"testing"
@ -224,8 +223,7 @@ func (suite *ASToInternalTestSuite) TestParseOwncastService() {
b, err := json.Marshal(apiAcct)
suite.NoError(err)
fmt.Printf("\n\n\n%s\n\n\n", string(b))
suite.NotNil(b)
}
func (suite *ASToInternalTestSuite) TestParseBookwyrmStatus() {
@ -282,6 +280,65 @@ func (suite *ASToInternalTestSuite) TestParseBookwyrmStatus() {
suite.Len(status.Attachments, 1)
}
func (suite *ASToInternalTestSuite) TestParseBandwagonAlbum() {
authorAccount := suite.testAccounts["remote_account_1"]
raw := `{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"discoverable": "toot:discoverable",
"indexable": "toot:indexable",
"toot": "https://joinmastodon.org/ns#"
},
"https://funkwhale.audio/ns"
],
"artists": [
{
"id": "https://bandwagon.fm/@67a0a0808121f77ed3466870",
"name": "Luka Prinčič",
"type": "Artist"
}
],
"attachment": [
{
"mediaType": "image/webp",
"name": "image",
"type": "Document",
"url": "https://bandwagon.fm/67a0a219f050061c8b4ce427/attachments/67a0a21bf050061c8b4ce429"
}
],
"attributedTo": "` + authorAccount.URI + `",
"content": "... a transgenre mutation, a fluid entity, jagged pop, electro-funk, techno-cabaret, a schlager, and soft alternative, queer to the core, satire and tragedy, sharp and fun indulgence for the dance of bodies and brains, activism and hedonism, which would all like to steal your attention.\r\n\r\nDRAGX̶FUNK is pronounced /dɹæɡɑːfʌŋk/.\r\n\r\n---\r\n\r\n## Buy digital\r\n💳 [Stripe](https://buy.stripe.com/6oE8x52iG1Kq5pKeV3)\r\n\r\n---\r\n\r\n## Buy dl/merch\r\n🎵 [Bandcamp](https://lukaprincic.bandcamp.com/album/dragx-funk) \r\n\r\n---\r\n\r\n## More:\r\n🌐 [prin.lu](https://prin.lu/music/241205_dragx-funk/) \r\n👉 [kamizdat.si](https://kamizdat.si/releases/dragx-funk-2/)\r\n",
"context": "https://bandwagon.fm/67a0a219f050061c8b4ce427",
"id": "https://bandwagon.fm/67a0a219f050061c8b4ce427",
"library": "https://bandwagon.fm/67a0a219f050061c8b4ce427/pub/children",
"license": "CC-BY-NC-SA",
"name": "DRAGX̶FUNK",
"published": "2025-03-17T11:40:53Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"tracks": "https://bandwagon.fm/67a0a219f050061c8b4ce427/pub/children",
"type": "Album",
"url": "https://bandwagon.fm/67a0a219f050061c8b4ce427"
}`
t := suite.jsonToType(raw)
asArticle, ok := t.(ap.Statusable)
if !ok {
suite.FailNow("type not coercible")
}
s, err := suite.typeconverter.ASStatusToStatus(context.Background(), asArticle)
if err != nil {
suite.FailNow(err.Error())
}
suite.NotNil(s)
suite.NoError(err)
}
func (suite *ASToInternalTestSuite) TestParseFlag1() {
reportedAccount := suite.testAccounts["local_account_1"]
reportingAccount := suite.testAccounts["remote_account_1"]

View file

@ -705,35 +705,38 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat
status.SetActivityStreamsSensitive(sensitiveProp)
// interactionPolicy
var p *gtsmodel.InteractionPolicy
if s.InteractionPolicy != nil {
// Use InteractionPolicy
// set on the status.
p = s.InteractionPolicy
} else {
// Fall back to default policy
// for the status's visibility.
p = gtsmodel.DefaultInteractionPolicyFor(s.Visibility)
}
policy, err := c.InteractionPolicyToASInteractionPolicy(ctx, p, s)
if err != nil {
return nil, fmt.Errorf("error creating interactionPolicy: %w", err)
}
policyProp := streams.NewGoToSocialInteractionPolicyProperty()
policyProp.AppendGoToSocialInteractionPolicy(policy)
status.SetGoToSocialInteractionPolicy(policyProp)
// Parse + set approvedBy.
if s.ApprovedByURI != "" {
approvedBy, err := url.Parse(s.ApprovedByURI)
if ipa, ok := status.(ap.InteractionPolicyAware); ok {
var p *gtsmodel.InteractionPolicy
if s.InteractionPolicy != nil {
// Use InteractionPolicy
// set on the status.
p = s.InteractionPolicy
} else {
// Fall back to default policy
// for the status's visibility.
p = gtsmodel.DefaultInteractionPolicyFor(s.Visibility)
}
policy, err := c.InteractionPolicyToASInteractionPolicy(ctx, p, s)
if err != nil {
return nil, fmt.Errorf("error parsing approvedBy: %w", err)
return nil, fmt.Errorf("error creating interactionPolicy: %w", err)
}
approvedByProp := streams.NewGoToSocialApprovedByProperty()
approvedByProp.Set(approvedBy)
status.SetGoToSocialApprovedBy(approvedByProp)
// Set interaction policy.
policyProp := streams.NewGoToSocialInteractionPolicyProperty()
policyProp.AppendGoToSocialInteractionPolicy(policy)
ipa.SetGoToSocialInteractionPolicy(policyProp)
// Parse + set approvedBy.
if s.ApprovedByURI != "" {
approvedBy, err := url.Parse(s.ApprovedByURI)
if err != nil {
return nil, fmt.Errorf("error parsing approvedBy: %w", err)
}
approvedByProp := streams.NewGoToSocialApprovedByProperty()
approvedByProp.Set(approvedBy)
ipa.SetGoToSocialApprovedBy(approvedByProp)
}
}
return status, nil