mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-11 08:27:29 -06:00
Merge branch 'main' into content_warning_fixes
This commit is contained in:
commit
aa5713fc38
26 changed files with 572 additions and 26 deletions
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/text"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
|
@ -106,11 +107,39 @@ type statusContent struct {
|
|||
Tags []*gtsmodel.Tag
|
||||
}
|
||||
|
||||
// Returns the final content type to use when creating or editing a status.
|
||||
func processContentType(
|
||||
requestContentType apimodel.StatusContentType,
|
||||
existingStatus *gtsmodel.Status,
|
||||
accountDefaultContentType string,
|
||||
) gtsmodel.StatusContentType {
|
||||
switch {
|
||||
// Content type set in the request, return the new value.
|
||||
case requestContentType != "":
|
||||
return typeutils.APIContentTypeToContentType(requestContentType)
|
||||
|
||||
// No content type in the request, return the existing
|
||||
// status's current content type if we know of one.
|
||||
case existingStatus != nil && existingStatus.ContentType != 0:
|
||||
return existingStatus.ContentType
|
||||
|
||||
// We aren't editing an existing status, or if we are
|
||||
// it's an old one that doesn't have a saved content
|
||||
// type. Use the user's default content type setting.
|
||||
case accountDefaultContentType != "":
|
||||
return typeutils.APIContentTypeToContentType(apimodel.StatusContentType(accountDefaultContentType))
|
||||
|
||||
// uhh.. Fall back to global default.
|
||||
default:
|
||||
return gtsmodel.StatusContentTypeDefault
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) processContent(
|
||||
ctx context.Context,
|
||||
author *gtsmodel.Account,
|
||||
statusID string,
|
||||
contentType string,
|
||||
contentType gtsmodel.StatusContentType,
|
||||
content string,
|
||||
contentWarning string,
|
||||
language string,
|
||||
|
|
@ -150,21 +179,15 @@ func (p *Processor) processContent(
|
|||
formatCW text.FormatFunc
|
||||
)
|
||||
|
||||
if contentType == "" {
|
||||
// If content type wasn't specified, use
|
||||
// the author's preferred content-type.
|
||||
contentType = author.Settings.StatusContentType
|
||||
}
|
||||
|
||||
switch contentType {
|
||||
|
||||
// Format status according to text/plain.
|
||||
case "", string(apimodel.StatusContentTypePlain):
|
||||
case gtsmodel.StatusContentTypePlain:
|
||||
format = p.formatter.FromPlain
|
||||
formatCW = p.formatter.FromPlainBasic
|
||||
|
||||
// Format status according to text/markdown.
|
||||
case string(apimodel.StatusContentTypeMarkdown):
|
||||
case gtsmodel.StatusContentTypeMarkdown:
|
||||
format = p.formatter.FromMarkdown
|
||||
formatCW = p.formatter.FromMarkdownBasic
|
||||
|
||||
|
|
|
|||
|
|
@ -66,11 +66,14 @@ func (p *Processor) Create(
|
|||
// Generate new ID for status.
|
||||
statusID := id.NewULID()
|
||||
|
||||
// Process incoming content type.
|
||||
contentType := processContentType(form.ContentType, nil, requester.Settings.StatusContentType)
|
||||
|
||||
// Process incoming status content fields.
|
||||
content, errWithCode := p.processContent(ctx,
|
||||
requester,
|
||||
statusID,
|
||||
string(form.ContentType),
|
||||
contentType,
|
||||
form.Status,
|
||||
form.SpoilerText,
|
||||
form.Language,
|
||||
|
|
@ -172,6 +175,7 @@ func (p *Processor) Create(
|
|||
ContentWarning: content.ContentWarning,
|
||||
Text: form.Status, // raw
|
||||
ContentWarningText: contentWarningText, // raw
|
||||
ContentType: contentType,
|
||||
|
||||
// Set gathered mentions.
|
||||
MentionIDs: content.MentionIDs,
|
||||
|
|
|
|||
|
|
@ -211,6 +211,36 @@ func (suite *StatusCreateTestSuite) TestProcessReplyToUnthreadedRemoteStatus() {
|
|||
suite.NotEmpty(dbStatus.ThreadID)
|
||||
}
|
||||
|
||||
func (suite *StatusCreateTestSuite) TestProcessNoContentTypeUsesDefault() {
|
||||
ctx := context.Background()
|
||||
creatingAccount := suite.testAccounts["local_account_1"]
|
||||
creatingApplication := suite.testApplications["application_1"]
|
||||
|
||||
statusCreateForm := &apimodel.StatusCreateRequest{
|
||||
Status: "poopoo peepee",
|
||||
SpoilerText: "",
|
||||
MediaIDs: []string{},
|
||||
Poll: nil,
|
||||
InReplyToID: "",
|
||||
Sensitive: false,
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
LocalOnly: util.Ptr(false),
|
||||
ScheduledAt: nil,
|
||||
Language: "en",
|
||||
ContentType: "",
|
||||
}
|
||||
|
||||
apiStatus, errWithCode := suite.status.Create(ctx, creatingAccount, creatingApplication, statusCreateForm)
|
||||
suite.NoError(errWithCode)
|
||||
suite.NotNil(apiStatus)
|
||||
|
||||
suite.Equal("<p>poopoo peepee</p>", apiStatus.Content)
|
||||
|
||||
// the test accounts don't have settings, so we're comparing to
|
||||
// the global default value instead of the requester's default
|
||||
suite.Equal(apimodel.StatusContentTypeDefault, apiStatus.ContentType)
|
||||
}
|
||||
|
||||
func TestStatusCreateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusCreateTestSuite))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,11 +84,14 @@ func (p *Processor) Edit(
|
|||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// Process incoming content type.
|
||||
contentType := processContentType(form.ContentType, status, requester.Settings.StatusContentType)
|
||||
|
||||
// Process incoming status edit content fields.
|
||||
content, errWithCode := p.processContent(ctx,
|
||||
requester,
|
||||
statusID,
|
||||
string(form.ContentType),
|
||||
contentType,
|
||||
form.Status,
|
||||
form.SpoilerText,
|
||||
form.Language,
|
||||
|
|
@ -256,6 +259,7 @@ func (p *Processor) Edit(
|
|||
edit.Content = status.Content
|
||||
edit.ContentWarning = status.ContentWarning
|
||||
edit.Text = status.Text
|
||||
edit.ContentType = status.ContentType
|
||||
edit.Language = status.Language
|
||||
edit.Sensitive = status.Sensitive
|
||||
edit.StatusID = status.ID
|
||||
|
|
@ -298,6 +302,7 @@ func (p *Processor) Edit(
|
|||
status.Content = content.Content
|
||||
status.ContentWarning = content.ContentWarning
|
||||
status.Text = form.Status // raw
|
||||
status.ContentType = contentType
|
||||
status.Language = content.Language
|
||||
status.Sensitive = &form.Sensitive
|
||||
status.AttachmentIDs = form.MediaIDs
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
|
||||
)
|
||||
|
|
@ -90,6 +91,142 @@ func (suite *StatusEditTestSuite) TestSimpleEdit() {
|
|||
previousEdit := latestStatus.Edits[len(latestStatus.Edits)-1]
|
||||
suite.Equal(status.Content, previousEdit.Content)
|
||||
suite.Equal(status.Text, previousEdit.Text)
|
||||
suite.Equal(status.ContentType, previousEdit.ContentType)
|
||||
suite.Equal(status.ContentWarning, previousEdit.ContentWarning)
|
||||
suite.Equal(*status.Sensitive, *previousEdit.Sensitive)
|
||||
suite.Equal(status.Language, previousEdit.Language)
|
||||
suite.Equal(status.UpdatedAt(), previousEdit.CreatedAt)
|
||||
}
|
||||
|
||||
func (suite *StatusEditTestSuite) TestEditChangeContentType() {
|
||||
// Create cancellable context to use for test.
|
||||
ctx, cncl := context.WithCancel(context.Background())
|
||||
defer cncl()
|
||||
|
||||
// Get a local account to use as test requester.
|
||||
requester := suite.testAccounts["local_account_1"]
|
||||
requester, _ = suite.state.DB.GetAccountByID(ctx, requester.ID)
|
||||
|
||||
// Get requester's existing plain text status to perform an edit on.
|
||||
status := suite.testStatuses["local_account_1_status_6"]
|
||||
status, _ = suite.state.DB.GetStatusByID(ctx, status.ID)
|
||||
|
||||
// Prepare edit with a Markdown body.
|
||||
form := &apimodel.StatusEditRequest{
|
||||
Status: "ooh the status is *fancy* now!",
|
||||
ContentType: apimodel.StatusContentTypeMarkdown,
|
||||
SpoilerText: "shhhhh",
|
||||
Sensitive: true,
|
||||
Language: "fr", // hoh hoh hoh
|
||||
MediaIDs: nil,
|
||||
MediaAttributes: nil,
|
||||
Poll: nil,
|
||||
}
|
||||
|
||||
// Pass the prepared form to the status processor to perform the edit.
|
||||
apiStatus, errWithCode := suite.status.Edit(ctx, requester, status.ID, form)
|
||||
suite.NotNil(apiStatus)
|
||||
suite.NoError(errWithCode)
|
||||
|
||||
// Check response against input form data.
|
||||
suite.Equal(form.Status, apiStatus.Text)
|
||||
suite.Equal(form.ContentType, apiStatus.ContentType)
|
||||
suite.Equal(form.SpoilerText, apiStatus.SpoilerText)
|
||||
suite.Equal(form.Sensitive, apiStatus.Sensitive)
|
||||
suite.Equal(form.Language, *apiStatus.Language)
|
||||
suite.NotEqual(util.FormatISO8601(status.EditedAt), *apiStatus.EditedAt)
|
||||
|
||||
// Fetched the latest version of edited status from the database.
|
||||
latestStatus, err := suite.state.DB.GetStatusByID(ctx, status.ID)
|
||||
suite.NoError(err)
|
||||
|
||||
// Check latest status against input form data.
|
||||
suite.Equal(form.Status, latestStatus.Text)
|
||||
suite.Equal(typeutils.APIContentTypeToContentType(form.ContentType), latestStatus.ContentType)
|
||||
suite.Equal(form.SpoilerText, latestStatus.ContentWarning)
|
||||
suite.Equal(form.Sensitive, *latestStatus.Sensitive)
|
||||
suite.Equal(form.Language, latestStatus.Language)
|
||||
suite.Equal(len(status.EditIDs)+1, len(latestStatus.EditIDs))
|
||||
suite.NotEqual(status.UpdatedAt(), latestStatus.UpdatedAt())
|
||||
|
||||
// Populate all historical edits for this status.
|
||||
err = suite.state.DB.PopulateStatusEdits(ctx, latestStatus)
|
||||
suite.NoError(err)
|
||||
|
||||
// Check previous status edit matches original status content.
|
||||
previousEdit := latestStatus.Edits[len(latestStatus.Edits)-1]
|
||||
suite.Equal(status.Content, previousEdit.Content)
|
||||
suite.Equal(status.Text, previousEdit.Text)
|
||||
suite.Equal(status.ContentType, previousEdit.ContentType)
|
||||
suite.Equal(status.ContentWarning, previousEdit.ContentWarning)
|
||||
suite.Equal(*status.Sensitive, *previousEdit.Sensitive)
|
||||
suite.Equal(status.Language, previousEdit.Language)
|
||||
suite.Equal(status.UpdatedAt(), previousEdit.CreatedAt)
|
||||
}
|
||||
|
||||
func (suite *StatusEditTestSuite) TestEditOnStatusWithNoContentType() {
|
||||
// Create cancellable context to use for test.
|
||||
ctx, cncl := context.WithCancel(context.Background())
|
||||
defer cncl()
|
||||
|
||||
// Get a local account to use as test requester.
|
||||
requester := suite.testAccounts["local_account_1"]
|
||||
requester, _ = suite.state.DB.GetAccountByID(ctx, requester.ID)
|
||||
|
||||
// Get requester's existing status, which has no
|
||||
// stored content type, to perform an edit on.
|
||||
status := suite.testStatuses["local_account_1_status_2"]
|
||||
status, _ = suite.state.DB.GetStatusByID(ctx, status.ID)
|
||||
|
||||
// Prepare edit without setting a new content type.
|
||||
form := &apimodel.StatusEditRequest{
|
||||
Status: "how will this text be parsed? it is a mystery",
|
||||
SpoilerText: "shhhhh",
|
||||
Sensitive: true,
|
||||
Language: "fr", // hoh hoh hoh
|
||||
MediaIDs: nil,
|
||||
MediaAttributes: nil,
|
||||
Poll: nil,
|
||||
}
|
||||
|
||||
// Pass the prepared form to the status processor to perform the edit.
|
||||
apiStatus, errWithCode := suite.status.Edit(ctx, requester, status.ID, form)
|
||||
suite.NotNil(apiStatus)
|
||||
suite.NoError(errWithCode)
|
||||
|
||||
// Check response against input form data.
|
||||
suite.Equal(form.Status, apiStatus.Text)
|
||||
suite.NotEqual(util.FormatISO8601(status.EditedAt), *apiStatus.EditedAt)
|
||||
|
||||
// Check response against requester's default content type setting
|
||||
// (the test accounts don't actually have settings on them, so
|
||||
// instead we check that the global default content type is used)
|
||||
suite.Equal(apimodel.StatusContentTypeDefault, apiStatus.ContentType)
|
||||
|
||||
// Fetched the latest version of edited status from the database.
|
||||
latestStatus, err := suite.state.DB.GetStatusByID(ctx, status.ID)
|
||||
suite.NoError(err)
|
||||
|
||||
// Check latest status against input form data
|
||||
suite.Equal(form.Status, latestStatus.Text)
|
||||
suite.Equal(form.Sensitive, *latestStatus.Sensitive)
|
||||
suite.Equal(form.Language, latestStatus.Language)
|
||||
suite.Equal(len(status.EditIDs)+1, len(latestStatus.EditIDs))
|
||||
suite.NotEqual(status.UpdatedAt(), latestStatus.UpdatedAt())
|
||||
|
||||
// Check latest status against requester's default content
|
||||
// type (again, actually just checking for the global default)
|
||||
suite.Equal(gtsmodel.StatusContentTypeDefault, latestStatus.ContentType)
|
||||
|
||||
// Populate all historical edits for this status.
|
||||
err = suite.state.DB.PopulateStatusEdits(ctx, latestStatus)
|
||||
suite.NoError(err)
|
||||
|
||||
// Check previous status edit matches original status content.
|
||||
previousEdit := latestStatus.Edits[len(latestStatus.Edits)-1]
|
||||
suite.Equal(status.Content, previousEdit.Content)
|
||||
suite.Equal(status.Text, previousEdit.Text)
|
||||
suite.Equal(status.ContentType, previousEdit.ContentType)
|
||||
suite.Equal(status.ContentWarning, previousEdit.ContentWarning)
|
||||
suite.Equal(*status.Sensitive, *previousEdit.Sensitive)
|
||||
suite.Equal(status.Language, previousEdit.Language)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
)
|
||||
|
||||
// Get gets the given status, taking account of privacy settings and blocks etc.
|
||||
|
|
@ -67,5 +68,6 @@ func (p *Processor) SourceGet(ctx context.Context, requester *gtsmodel.Account,
|
|||
ID: status.ID,
|
||||
Text: status.Text,
|
||||
SpoilerText: spoilerText,
|
||||
ContentType: typeutils.ContentTypeToAPIContentType(status.ContentType),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue