[feature] Parse content warning to HTML, serialize via client API as plaintext (#3876)
* [feature] Parse content warning as HTML, serialize via API to plaintext * tidy up some cruft * whoops * oops * i'm da joker baybee * clemency muy lorde * rename some of the text functions for clarity * jiggle the opts * fiddle de deee * hopefully the last test fix i ever have to do in my beautiful life
This commit is contained in:
parent
424f62dd70
commit
d8113c11e4
|
@ -113,7 +113,7 @@ func normalizeContent(rawContent interface{}) string {
|
|||
//
|
||||
// TODO: sanitize differently based on mediaType.
|
||||
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-mediatype
|
||||
content = text.SanitizeToHTML(content)
|
||||
content = text.SanitizeHTML(content)
|
||||
content = text.MinifyHTML(content)
|
||||
return content
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ func NormalizeIncomingSummary(item WithSummary, rawJSON map[string]interface{})
|
|||
|
||||
// Summary should be HTML encoded:
|
||||
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-summary
|
||||
summary = text.SanitizeToHTML(summary)
|
||||
summary = text.SanitizeHTML(summary)
|
||||
summary = text.MinifyHTML(summary)
|
||||
|
||||
// Set normalized summary property from the raw string; this
|
||||
|
@ -339,7 +339,7 @@ func NormalizeIncomingName(item WithName, rawJSON map[string]interface{}) {
|
|||
//
|
||||
// todo: We probably want to update this to allow
|
||||
// *escaped* HTML markup, but for now just nuke it.
|
||||
name = text.SanitizeToPlaintext(name)
|
||||
name = text.StripHTMLFromText(name)
|
||||
|
||||
// Set normalized name property from the raw string; this
|
||||
// will replace any existing name property on the item.
|
||||
|
@ -369,7 +369,7 @@ func NormalizeIncomingValue(item WithValue, rawJSON map[string]interface{}) {
|
|||
// Value often contains links or
|
||||
// mentions or other little snippets.
|
||||
// Sanitize to HTML to allow these.
|
||||
value = text.SanitizeToHTML(value)
|
||||
value = text.SanitizeHTML(value)
|
||||
|
||||
// Set normalized name property from the raw string; this
|
||||
// will replace any existing value property on the item.
|
||||
|
|
|
@ -508,7 +508,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
|
|||
"muted": false,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "dark souls status bot: \"thoughts of dog\"",
|
||||
"content": "\u003cp\u003edark souls status bot: \"thoughts of dog\"\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
|
@ -765,7 +765,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetCreatedByAccount() {
|
|||
"muted": false,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "dark souls status bot: \"thoughts of dog\"",
|
||||
"content": "\u003cp\u003edark souls status bot: \"thoughts of dog\"\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
|
@ -1022,7 +1022,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetTargetAccount() {
|
|||
"muted": false,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "dark souls status bot: \"thoughts of dog\"",
|
||||
"content": "\u003cp\u003edark souls status bot: \"thoughts of dog\"\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
|
|
|
@ -916,7 +916,7 @@ func (suite *SearchGetTestSuite) TestSearchAAny() {
|
|||
}
|
||||
|
||||
suite.Len(searchResult.Accounts, 5)
|
||||
suite.Len(searchResult.Statuses, 8)
|
||||
suite.Len(searchResult.Statuses, 9)
|
||||
suite.Len(searchResult.Hashtags, 0)
|
||||
}
|
||||
|
||||
|
@ -959,7 +959,7 @@ func (suite *SearchGetTestSuite) TestSearchAAnyFollowingOnly() {
|
|||
}
|
||||
|
||||
suite.Len(searchResult.Accounts, 2)
|
||||
suite.Len(searchResult.Statuses, 8)
|
||||
suite.Len(searchResult.Statuses, 9)
|
||||
suite.Len(searchResult.Hashtags, 0)
|
||||
}
|
||||
|
||||
|
@ -1002,7 +1002,7 @@ func (suite *SearchGetTestSuite) TestSearchAStatuses() {
|
|||
}
|
||||
|
||||
suite.Len(searchResult.Accounts, 0)
|
||||
suite.Len(searchResult.Statuses, 8)
|
||||
suite.Len(searchResult.Statuses, 9)
|
||||
suite.Len(searchResult.Hashtags, 0)
|
||||
}
|
||||
|
||||
|
|
|
@ -144,7 +144,7 @@ func (suite *StatusBoostTestSuite) TestPostBoost() {
|
|||
},
|
||||
"bookmarked": true,
|
||||
"card": null,
|
||||
"content": "hello world! #welcome ! first post on the instance :rainbow: !",
|
||||
"content": "<p>hello world! <a href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>welcome</span></a> ! first post on the instance :rainbow: !</p>",
|
||||
"content_type": "text/plain",
|
||||
"created_at": "right the hell just now babyee",
|
||||
"edited_at": null,
|
||||
|
@ -331,7 +331,7 @@ func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() {
|
|||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "hi!",
|
||||
"content": "<p>hi!</p>",
|
||||
"content_type": "text/plain",
|
||||
"created_at": "right the hell just now babyee",
|
||||
"edited_at": null,
|
||||
|
|
|
@ -103,7 +103,7 @@ func (suite *StatusFaveTestSuite) TestPostFave() {
|
|||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "🐕🐕🐕🐕🐕",
|
||||
"content": "<p>🐕🐕🐕🐕🐕</p>",
|
||||
"content_type": "text/plain",
|
||||
"created_at": "right the hell just now babyee",
|
||||
"edited_at": null,
|
||||
|
|
|
@ -91,7 +91,7 @@ func (suite *StatusHistoryTestSuite) TestGetHistory() {
|
|||
|
||||
suite.Equal(`[
|
||||
{
|
||||
"content": "hello everyone!",
|
||||
"content": "\u003cp\u003ehello everyone!\u003c/p\u003e",
|
||||
"spoiler_text": "introduction post",
|
||||
"sensitive": true,
|
||||
"created_at": "2021-10-20T10:40:37.000Z",
|
||||
|
|
|
@ -108,7 +108,7 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
|
|||
"muted": true,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "hello everyone!",
|
||||
"content": "\u003cp\u003ehello everyone!\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "really cool gts application",
|
||||
|
@ -198,7 +198,7 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
|
|||
"muted": false,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "hello everyone!",
|
||||
"content": "\u003cp\u003ehello everyone!\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "really cool gts application",
|
||||
|
|
|
@ -129,7 +129,6 @@ func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() {
|
|||
err = json.Unmarshal(b, statusReply)
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText)
|
||||
assert.Equal(suite.T(), targetStatus.Content, statusReply.Content)
|
||||
assert.True(suite.T(), statusReply.Sensitive)
|
||||
assert.Equal(suite.T(), apimodel.VisibilityPublic, statusReply.Visibility)
|
||||
|
|
|
@ -127,6 +127,10 @@ type Status struct {
|
|||
type WebStatus struct {
|
||||
*Status
|
||||
|
||||
// HTML version of spoiler content
|
||||
// (ie., not converted to plaintext).
|
||||
SpoilerContent string `json:"-"`
|
||||
|
||||
// Override API account with web account.
|
||||
Account *WebAccount `json:"account"`
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ func OGBase(instance *apimodel.InstanceV1) *OGMeta {
|
|||
}
|
||||
|
||||
og := &OGMeta{
|
||||
Title: text.SanitizeToPlaintext(instance.Title) + " - GoToSocial",
|
||||
Title: text.StripHTMLFromText(instance.Title) + " - GoToSocial",
|
||||
Type: "website",
|
||||
Locale: locale,
|
||||
URL: instance.URI,
|
||||
|
@ -161,7 +161,7 @@ func AccountTitle(account *apimodel.WebAccount, accountDomain string) string {
|
|||
// ParseDescription returns a string description which is
|
||||
// safe to use as a template.HTMLAttr inside templates.
|
||||
func ParseDescription(in string) string {
|
||||
i := text.SanitizeToPlaintext(in)
|
||||
i := text.StripHTMLFromText(in)
|
||||
i = strings.ReplaceAll(i, "\n", " ")
|
||||
i = strings.Join(strings.Fields(i), " ")
|
||||
i = html.EscapeString(i)
|
||||
|
|
|
@ -666,6 +666,7 @@ func sizeofStatus() uintptr {
|
|||
BoostOfID: exampleID,
|
||||
BoostOfAccountID: exampleID,
|
||||
ContentWarning: exampleUsername, // similar length
|
||||
ContentWarningText: exampleUsername, // similar length
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: func() *bool { ok := false; return &ok }(),
|
||||
Language: "en",
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
newmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20250305205820_content_warning_fixes"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
var newStatus *newmodel.Status
|
||||
newStatusType := reflect.TypeOf(newStatus)
|
||||
|
||||
// Generate new Status.ContentWarningText column definition from bun.
|
||||
colDef, err := getBunColumnDef(tx, newStatusType, "ContentWarningText")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making column def: %w", err)
|
||||
}
|
||||
|
||||
log.Info(ctx, "adding statuses.content_warning_text column...")
|
||||
_, err = tx.NewAddColumn().Model(newStatus).
|
||||
ColumnExpr(colDef).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding column: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gtsmodel
|
||||
|
||||
type PolicyValue string
|
||||
type PolicyValues []PolicyValue
|
||||
|
||||
type InteractionPolicy struct {
|
||||
CanLike PolicyRules
|
||||
CanReply PolicyRules
|
||||
CanAnnounce PolicyRules
|
||||
}
|
||||
|
||||
type PolicyRules struct {
|
||||
Always PolicyValues
|
||||
WithApproval PolicyValues
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gtsmodel
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
||||
EditedAt time.Time `bun:"type:timestamptz,nullzero"`
|
||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"`
|
||||
PinnedAt time.Time `bun:"type:timestamptz,nullzero"`
|
||||
URI string `bun:",unique,nullzero,notnull"`
|
||||
URL string `bun:",nullzero"`
|
||||
Content string `bun:""`
|
||||
AttachmentIDs []string `bun:"attachments,array"`
|
||||
TagIDs []string `bun:"tags,array"`
|
||||
MentionIDs []string `bun:"mentions,array"`
|
||||
EmojiIDs []string `bun:"emojis,array"`
|
||||
Local *bool `bun:",nullzero,notnull,default:false"`
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"`
|
||||
AccountURI string `bun:",nullzero,notnull"`
|
||||
InReplyToID string `bun:"type:CHAR(26),nullzero"`
|
||||
InReplyToURI string `bun:",nullzero"`
|
||||
InReplyToAccountID string `bun:"type:CHAR(26),nullzero"`
|
||||
BoostOfID string `bun:"type:CHAR(26),nullzero"`
|
||||
BoostOfAccountID string `bun:"type:CHAR(26),nullzero"`
|
||||
ThreadID string `bun:"type:CHAR(26),nullzero"`
|
||||
EditIDs []string `bun:"edits,array"`
|
||||
PollID string `bun:"type:CHAR(26),nullzero"`
|
||||
ContentWarning string `bun:",nullzero"`
|
||||
ContentWarningText string `bun:""`
|
||||
Visibility Visibility `bun:",nullzero,notnull"`
|
||||
Sensitive *bool `bun:",nullzero,notnull,default:false"`
|
||||
Language string `bun:",nullzero"`
|
||||
CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"`
|
||||
ActivityStreamsType string `bun:",nullzero,notnull"`
|
||||
Text string `bun:""`
|
||||
Federated *bool `bun:",notnull"`
|
||||
InteractionPolicy *InteractionPolicy `bun:""`
|
||||
PendingApproval *bool `bun:",nullzero,notnull,default:false"`
|
||||
ApprovedByURI string `bun:",nullzero"`
|
||||
}
|
||||
|
||||
type Visibility int16
|
|
@ -33,7 +33,7 @@ type Status struct {
|
|||
PinnedAt time.Time `bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time.
|
||||
URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this status
|
||||
URL string `bun:",nullzero"` // web url for viewing this status
|
||||
Content string `bun:""` // content of this status; likely html-formatted but not guaranteed
|
||||
Content string `bun:""` // Content HTML for this status.
|
||||
AttachmentIDs []string `bun:"attachments,array"` // Database IDs of any media attachments associated with this status
|
||||
Attachments []*MediaAttachment `bun:"attached_media,rel:has-many"` // Attachments corresponding to attachmentIDs
|
||||
TagIDs []string `bun:"tags,array"` // Database IDs of any tags used in this status
|
||||
|
@ -61,7 +61,8 @@ type Status struct {
|
|||
Edits []*StatusEdit `bun:"-"` //
|
||||
PollID string `bun:"type:CHAR(26),nullzero"` //
|
||||
Poll *Poll `bun:"-"` //
|
||||
ContentWarning string `bun:",nullzero"` // cw string for this status
|
||||
ContentWarning string `bun:",nullzero"` // Content warning HTML for this status.
|
||||
ContentWarningText string `bun:""` // Original text of the content warning without formatting
|
||||
Visibility Visibility `bun:",nullzero,notnull"` // visibility entry for this status
|
||||
Sensitive *bool `bun:",nullzero,notnull,default:false"` // mark the status as sensitive?
|
||||
Language string `bun:",nullzero"` // what language is this status written in?
|
||||
|
|
|
@ -43,10 +43,10 @@ func (suite *GetRSSTestSuite) TestGetAccountRSSAdmin() {
|
|||
<pubDate>Wed, 20 Oct 2021 10:41:37 +0000</pubDate>
|
||||
<lastBuildDate>Wed, 20 Oct 2021 10:41:37 +0000</lastBuildDate>
|
||||
<item>
|
||||
<title>open to see some puppies</title>
|
||||
<title>open to see some <strong>puppies</strong></title>
|
||||
<link>http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37</link>
|
||||
<description>@admin@localhost:8080 made a new post: "🐕🐕🐕🐕🐕"</description>
|
||||
<content:encoded><![CDATA[🐕🐕🐕🐕🐕]]></content:encoded>
|
||||
<content:encoded><![CDATA[<p>🐕🐕🐕🐕🐕</p>]]></content:encoded>
|
||||
<author>@admin@localhost:8080</author>
|
||||
<guid isPermaLink="true">http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37</guid>
|
||||
<pubDate>Wed, 20 Oct 2021 12:36:45 +0000</pubDate>
|
||||
|
@ -56,7 +56,7 @@ func (suite *GetRSSTestSuite) TestGetAccountRSSAdmin() {
|
|||
<title>hello world! #welcome ! first post on the instance :rainbow: !</title>
|
||||
<link>http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R</link>
|
||||
<description>@admin@localhost:8080 posted 1 attachment: "hello world! #welcome ! first post on the instance :rainbow: !"</description>
|
||||
<content:encoded><![CDATA[hello world! #welcome ! first post on the instance <img src="http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png" title=":rainbow:" alt=":rainbow:" width="25" height="25" /> !]]></content:encoded>
|
||||
<content:encoded><![CDATA[<p>hello world! <a href="http://localhost:8080/tags/welcome" class="mention hashtag" rel="tag nofollow noreferrer noopener" target="_blank">#<span>welcome</span></a> ! first post on the instance <img src="http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png" title=":rainbow:" alt=":rainbow:" width="25" height="25" /> !</p>]]></content:encoded>
|
||||
<author>@admin@localhost:8080</author>
|
||||
<enclosure url="http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg" length="62529" type="image/jpeg"></enclosure>
|
||||
<guid isPermaLink="true">http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R</guid>
|
||||
|
@ -145,7 +145,7 @@ func (suite *GetRSSTestSuite) TestGetAccountRSSZork() {
|
|||
<title>introduction post</title>
|
||||
<link>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</link>
|
||||
<description>@the_mighty_zork@localhost:8080 made a new post: "hello everyone!"</description>
|
||||
<content:encoded><![CDATA[hello everyone!]]></content:encoded>
|
||||
<content:encoded><![CDATA[<p>hello everyone!</p>]]></content:encoded>
|
||||
<author>@the_mighty_zork@localhost:8080</author>
|
||||
<guid isPermaLink="true">http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</guid>
|
||||
<pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>
|
||||
|
|
|
@ -97,8 +97,8 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
|||
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||
}
|
||||
|
||||
// Parse new display name (always from plaintext).
|
||||
account.DisplayName = text.SanitizeToPlaintext(displayName)
|
||||
// HTML tags not allowed in display name.
|
||||
account.DisplayName = text.StripHTMLFromText(displayName)
|
||||
acctColumns = append(acctColumns, "display_name")
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
|||
}
|
||||
|
||||
if form.AvatarDescription != nil {
|
||||
desc := text.SanitizeToPlaintext(*form.AvatarDescription)
|
||||
desc := text.StripHTMLFromText(*form.AvatarDescription)
|
||||
form.AvatarDescription = &desc
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
|||
}
|
||||
|
||||
if form.HeaderDescription != nil {
|
||||
desc := text.SanitizeToPlaintext(*form.HeaderDescription)
|
||||
desc := text.StripHTMLFromText(*form.HeaderDescription)
|
||||
form.HeaderDescription = util.Ptr(desc)
|
||||
}
|
||||
|
||||
|
@ -265,7 +265,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
|||
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||
}
|
||||
|
||||
account.Settings.CustomCSS = text.SanitizeToPlaintext(customCSS)
|
||||
account.Settings.CustomCSS = text.StripHTMLFromText(customCSS)
|
||||
settingsColumns = append(settingsColumns, "custom_css")
|
||||
}
|
||||
|
||||
|
@ -356,8 +356,8 @@ func (p *Processor) updateFields(
|
|||
|
||||
// Sanitize raw field values.
|
||||
fieldRaw := >smodel.Field{
|
||||
Name: text.SanitizeToPlaintext(name),
|
||||
Value: text.SanitizeToPlaintext(value),
|
||||
Name: text.StripHTMLFromText(name),
|
||||
Value: text.StripHTMLFromText(value),
|
||||
}
|
||||
fieldsRaw = append(fieldsRaw, fieldRaw)
|
||||
}
|
||||
|
@ -385,7 +385,7 @@ func (p *Processor) processAccountText(
|
|||
emojis := make(map[string]*gtsmodel.Emoji)
|
||||
|
||||
// Retrieve display name emojis.
|
||||
for _, emoji := range p.formatter.FromPlainEmojiOnly(
|
||||
for _, emoji := range p.formatter.FromPlainBasic(
|
||||
ctx,
|
||||
p.parseMention,
|
||||
account.ID,
|
||||
|
@ -413,7 +413,7 @@ func (p *Processor) processAccountText(
|
|||
// Name stays plain, but we still need to
|
||||
// see if there are any emojis set in it.
|
||||
field.Name = fieldRaw.Name
|
||||
for _, emoji := range p.formatter.FromPlainEmojiOnly(
|
||||
for _, emoji := range p.formatter.FromPlainBasic(
|
||||
ctx,
|
||||
p.parseMention,
|
||||
account.ID,
|
||||
|
|
|
@ -53,8 +53,8 @@ func (p *Processor) createDomainAllow(
|
|||
ID: id.NewULID(),
|
||||
Domain: domain,
|
||||
CreatedByAccountID: adminAcct.ID,
|
||||
PrivateComment: text.SanitizeToPlaintext(privateComment),
|
||||
PublicComment: text.SanitizeToPlaintext(publicComment),
|
||||
PrivateComment: text.StripHTMLFromText(privateComment),
|
||||
PublicComment: text.StripHTMLFromText(publicComment),
|
||||
Obfuscate: &obfuscate,
|
||||
SubscriptionID: subscriptionID,
|
||||
}
|
||||
|
|
|
@ -53,8 +53,8 @@ func (p *Processor) createDomainBlock(
|
|||
ID: id.NewULID(),
|
||||
Domain: domain,
|
||||
CreatedByAccountID: adminAcct.ID,
|
||||
PrivateComment: text.SanitizeToPlaintext(privateComment),
|
||||
PublicComment: text.SanitizeToPlaintext(publicComment),
|
||||
PrivateComment: text.StripHTMLFromText(privateComment),
|
||||
PublicComment: text.StripHTMLFromText(publicComment),
|
||||
Obfuscate: &obfuscate,
|
||||
SubscriptionID: subscriptionID,
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ func (p *Processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe
|
|||
}
|
||||
|
||||
// Don't allow html in site title.
|
||||
instance.Title = text.SanitizeToPlaintext(title)
|
||||
instance.Title = text.StripHTMLFromText(title)
|
||||
columns = append(columns, "title")
|
||||
}
|
||||
|
||||
|
@ -235,7 +235,7 @@ func (p *Processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe
|
|||
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||
}
|
||||
|
||||
instance.CustomCSS = text.SanitizeToPlaintext(customCSS)
|
||||
instance.CustomCSS = text.StripHTMLFromText(customCSS)
|
||||
columns = append(columns, []string{"custom_css"}...)
|
||||
}
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, media
|
|||
|
||||
// processDescription will sanitize and valid description against server configuration.
|
||||
func processDescription(description string) (string, gtserror.WithCode) {
|
||||
description = text.SanitizeToPlaintext(description)
|
||||
description = text.StripHTMLFromText(description)
|
||||
chars := len([]rune(description))
|
||||
|
||||
if min := config.GetMediaDescriptionMinChars(); chars < min {
|
||||
|
|
|
@ -171,19 +171,25 @@ func (p *Processor) processContent(
|
|||
)
|
||||
}
|
||||
|
||||
var (
|
||||
// format is the currently set text formatting
|
||||
// function, according to the provided content-type.
|
||||
var format text.FormatFunc
|
||||
format text.FormatFunc
|
||||
// formatCW is like format, but for content warning.
|
||||
formatCW text.FormatFunc
|
||||
)
|
||||
|
||||
switch contentType {
|
||||
|
||||
// Format status according to text/plain.
|
||||
case gtsmodel.StatusContentTypePlain:
|
||||
format = p.formatter.FromPlain
|
||||
formatCW = p.formatter.FromPlainBasic
|
||||
|
||||
// Format status according to text/markdown.
|
||||
case gtsmodel.StatusContentTypeMarkdown:
|
||||
format = p.formatter.FromMarkdown
|
||||
formatCW = p.formatter.FromMarkdownBasic
|
||||
|
||||
// Unknown.
|
||||
default:
|
||||
|
@ -215,26 +221,23 @@ func (p *Processor) processContent(
|
|||
status.Emojis = contentRes.Emojis
|
||||
status.Tags = contentRes.Tags
|
||||
|
||||
// From here-on-out just use emoji-only
|
||||
// plain-text formatting as the FormatFunc.
|
||||
format = p.formatter.FromPlainEmojiOnly
|
||||
|
||||
// Sanitize content warning and format.
|
||||
warning := text.SanitizeToPlaintext(contentWarning)
|
||||
warningRes := formatInput(format, warning)
|
||||
cwRes := formatInput(formatCW, contentWarning)
|
||||
|
||||
// Gather results of the formatted.
|
||||
status.ContentWarning = warningRes.HTML
|
||||
status.Emojis = append(status.Emojis, warningRes.Emojis...)
|
||||
status.ContentWarning = cwRes.HTML
|
||||
status.Emojis = append(status.Emojis, cwRes.Emojis...)
|
||||
|
||||
if poll != nil {
|
||||
// Pre-allocate slice of poll options of expected length.
|
||||
status.PollOptions = make([]string, len(poll.Options))
|
||||
for i, option := range poll.Options {
|
||||
|
||||
// Sanitize each poll option and format.
|
||||
option = text.SanitizeToPlaintext(option)
|
||||
optionRes := formatInput(format, option)
|
||||
// Strip each poll option and format.
|
||||
//
|
||||
// For polls just use basic formatting.
|
||||
option = text.StripHTMLFromText(option)
|
||||
optionRes := formatInput(p.formatter.FromPlainBasic, option)
|
||||
|
||||
// Gather results of the formatted.
|
||||
status.PollOptions[i] = optionRes.HTML
|
||||
|
|
|
@ -189,6 +189,13 @@ func (p *Processor) Create(
|
|||
PendingApproval: util.Ptr(false),
|
||||
}
|
||||
|
||||
// Only store ContentWarningText if the parsed
|
||||
// result is different from the given SpoilerText,
|
||||
// otherwise skip to avoid duplicating db columns.
|
||||
if content.ContentWarning != form.SpoilerText {
|
||||
status.ContentWarningText = form.SpoilerText
|
||||
}
|
||||
|
||||
if backfill {
|
||||
// Ensure backfilled status contains no
|
||||
// mentions to anyone other than author.
|
||||
|
|
|
@ -60,33 +60,6 @@ func (suite *StatusCreateTestSuite) TestProcessContentWarningWithQuotationMarks(
|
|||
suite.Equal("\"test\"", apiStatus.SpoilerText)
|
||||
}
|
||||
|
||||
func (suite *StatusCreateTestSuite) TestProcessContentWarningWithHTMLEscapedQuotationMarks() {
|
||||
ctx := context.Background()
|
||||
|
||||
creatingAccount := suite.testAccounts["local_account_1"]
|
||||
creatingApplication := suite.testApplications["application_1"]
|
||||
|
||||
statusCreateForm := &apimodel.StatusCreateRequest{
|
||||
Status: "poopoo peepee",
|
||||
MediaIDs: []string{},
|
||||
Poll: nil,
|
||||
InReplyToID: "",
|
||||
Sensitive: false,
|
||||
SpoilerText: ""test"", // the html-escaped quotation marks should appear as normal quotation marks in the finished text
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
LocalOnly: util.Ptr(false),
|
||||
ScheduledAt: nil,
|
||||
Language: "en",
|
||||
ContentType: apimodel.StatusContentTypePlain,
|
||||
}
|
||||
|
||||
apiStatus, err := suite.status.Create(ctx, creatingAccount, creatingApplication, statusCreateForm)
|
||||
suite.NoError(err)
|
||||
suite.NotNil(apiStatus)
|
||||
|
||||
suite.Equal("\"test\"", apiStatus.SpoilerText)
|
||||
}
|
||||
|
||||
func (suite *StatusCreateTestSuite) TestProcessStatusMarkdownWithUnderscoreEmoji() {
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
|
@ -50,6 +50,13 @@ func (p *Processor) Delete(ctx context.Context, requestingAccount *gtsmodel.Acco
|
|||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// Replace content warning with raw
|
||||
// version if it's available, to make
|
||||
// delete + redraft work nicer.
|
||||
if targetStatus.ContentWarningText != "" {
|
||||
apiStatus.SpoilerText = targetStatus.ContentWarningText
|
||||
}
|
||||
|
||||
// Process delete side effects.
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
|
|
|
@ -301,7 +301,7 @@ func (p *Processor) Edit(
|
|||
// update the other necessary status fields.
|
||||
status.Content = content.Content
|
||||
status.ContentWarning = content.ContentWarning
|
||||
status.Text = form.Status
|
||||
status.Text = form.Status // raw
|
||||
status.ContentType = contentType
|
||||
status.Language = content.Language
|
||||
status.Sensitive = &form.Sensitive
|
||||
|
@ -309,6 +309,13 @@ func (p *Processor) Edit(
|
|||
status.Attachments = media
|
||||
status.EditedAt = now
|
||||
|
||||
// Only store ContentWarningText if the parsed
|
||||
// result is different from the given SpoilerText,
|
||||
// otherwise skip to avoid duplicating db columns.
|
||||
if content.ContentWarning != form.SpoilerText {
|
||||
status.ContentWarningText = form.SpoilerText
|
||||
}
|
||||
|
||||
if poll != nil {
|
||||
// Set relevent fields for latest with poll.
|
||||
status.ActivityStreamsType = ap.ActivityQuestion
|
||||
|
|
|
@ -53,10 +53,21 @@ func (p *Processor) SourceGet(ctx context.Context, requester *gtsmodel.Account,
|
|||
"target status not found",
|
||||
)
|
||||
}
|
||||
|
||||
// Try to use unparsed content
|
||||
// warning text if available,
|
||||
// fall back to parsed cw html.
|
||||
var spoilerText string
|
||||
if status.ContentWarningText != "" {
|
||||
spoilerText = status.ContentWarningText
|
||||
} else {
|
||||
spoilerText = status.ContentWarning
|
||||
}
|
||||
|
||||
return &apimodel.StatusSource{
|
||||
ID: status.ID,
|
||||
Text: status.Text,
|
||||
SpoilerText: status.ContentWarning,
|
||||
SpoilerText: spoilerText,
|
||||
ContentType: typeutils.ContentTypeToAPIContentType(status.ContentType),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func (suite *StatusUpdateTestSuite) TestStreamNotification() {
|
|||
"muted": false,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "dark souls status bot: \"thoughts of dog\"",
|
||||
"content": "\u003cp\u003edark souls status bot: \"thoughts of dog\"\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
|
|
|
@ -122,7 +122,7 @@ func (p *Processor) Create(
|
|||
Username: form.Username,
|
||||
Email: form.Email,
|
||||
Password: form.Password,
|
||||
Reason: text.SanitizeToPlaintext(reason),
|
||||
Reason: text.StripHTMLFromText(reason),
|
||||
SignUpIP: form.IP,
|
||||
Locale: form.Locale,
|
||||
AppID: app.ID,
|
||||
|
|
|
@ -93,6 +93,16 @@ func (suite *TextStandardTestSuite) FromMarkdown(input string) *text.FormatResul
|
|||
)
|
||||
}
|
||||
|
||||
func (suite *TextStandardTestSuite) FromMarkdownBasic(input string) *text.FormatResult {
|
||||
return suite.formatter.FromMarkdownBasic(
|
||||
context.Background(),
|
||||
suite.parseMention,
|
||||
suite.testAccounts["local_account_1"].ID,
|
||||
"dummy_status_ID",
|
||||
input,
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *TextStandardTestSuite) FromPlain(input string) *text.FormatResult {
|
||||
return suite.formatter.FromPlain(
|
||||
context.Background(),
|
||||
|
|
|
@ -20,6 +20,8 @@ package text
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
|
@ -27,11 +29,15 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/regexes"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
)
|
||||
|
||||
// FromMarkdown fulfils FormatFunc by parsing
|
||||
// the given markdown input into a FormatResult.
|
||||
//
|
||||
// Inline (aka unsafe) HTML elements are allowed,
|
||||
// as they should be sanitized afterwards anyway.
|
||||
func (f *Formatter) FromMarkdown(
|
||||
ctx context.Context,
|
||||
parseMention gtsmodel.ParseMentionFunc,
|
||||
|
@ -39,18 +45,85 @@ func (f *Formatter) FromMarkdown(
|
|||
statusID string,
|
||||
input string,
|
||||
) *FormatResult {
|
||||
result := new(FormatResult)
|
||||
return f.fromMarkdown(
|
||||
ctx,
|
||||
false, // basic = false
|
||||
parseMention,
|
||||
authorID,
|
||||
statusID,
|
||||
input,
|
||||
)
|
||||
}
|
||||
|
||||
// FromMarkdownBasic fulfils FormatFunc by parsing
|
||||
// the given markdown input into a FormatResult.
|
||||
//
|
||||
// Unlike FromMarkdown, it will only parse emojis with
|
||||
// the custom renderer, leaving aside mentions and tags.
|
||||
//
|
||||
// Inline (aka unsafe) HTML elements are not allowed.
|
||||
//
|
||||
// If the result is a single paragraph,
|
||||
// it will not be wrapped in <p> tags.
|
||||
func (f *Formatter) FromMarkdownBasic(
|
||||
ctx context.Context,
|
||||
parseMention gtsmodel.ParseMentionFunc,
|
||||
authorID string,
|
||||
statusID string,
|
||||
input string,
|
||||
) *FormatResult {
|
||||
res := f.fromMarkdown(
|
||||
ctx,
|
||||
true, // basic = true
|
||||
parseMention,
|
||||
authorID,
|
||||
statusID,
|
||||
input,
|
||||
)
|
||||
|
||||
res.HTML = unwrapParagraph(res.HTML)
|
||||
return res
|
||||
}
|
||||
|
||||
// fromMarkdown parses the given input text either
|
||||
// with or without emojis, and returns the result.
|
||||
func (f *Formatter) fromMarkdown(
|
||||
ctx context.Context,
|
||||
basic bool,
|
||||
parseMention gtsmodel.ParseMentionFunc,
|
||||
authorID string,
|
||||
statusID string,
|
||||
input string,
|
||||
) *FormatResult {
|
||||
var (
|
||||
result = new(FormatResult)
|
||||
opts []renderer.Option
|
||||
)
|
||||
|
||||
if basic {
|
||||
// Don't allow raw HTML tags,
|
||||
// markdown syntax only.
|
||||
opts = []renderer.Option{
|
||||
html.WithXHTML(),
|
||||
html.WithHardWraps(),
|
||||
}
|
||||
} else {
|
||||
opts = []renderer.Option{
|
||||
html.WithXHTML(),
|
||||
html.WithHardWraps(),
|
||||
|
||||
// Allow raw HTML tags, we
|
||||
// sanitize at the end anyway.
|
||||
html.WithUnsafe(),
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate goldmark parser for
|
||||
// markdown, using custom renderer
|
||||
// to add hashtag/mention links.
|
||||
md := goldmark.New(
|
||||
goldmark.WithRendererOptions(
|
||||
html.WithXHTML(),
|
||||
html.WithHardWraps(),
|
||||
// Allows raw HTML. We sanitize
|
||||
// at the end so this is OK.
|
||||
html.WithUnsafe(),
|
||||
opts...,
|
||||
),
|
||||
goldmark.WithExtensions(
|
||||
&customRenderer{
|
||||
|
@ -59,7 +132,9 @@ func (f *Formatter) FromMarkdown(
|
|||
parseMention,
|
||||
authorID,
|
||||
statusID,
|
||||
false, // emojiOnly = false.
|
||||
// If basic, pass
|
||||
// emojiOnly = true.
|
||||
basic,
|
||||
result,
|
||||
},
|
||||
// Turns URLs into links.
|
||||
|
@ -85,8 +160,36 @@ func (f *Formatter) FromMarkdown(
|
|||
|
||||
// Clean and shrink HTML.
|
||||
result.HTML = byteutil.B2S(htmlBytes.Bytes())
|
||||
result.HTML = SanitizeToHTML(result.HTML)
|
||||
result.HTML = SanitizeHTML(result.HTML)
|
||||
result.HTML = MinifyHTML(result.HTML)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var parasRegexp = regexp.MustCompile(`</?p>`)
|
||||
|
||||
// unwrapParagraph removes opening and closing paragraph tags
|
||||
// of input HTML, if input html is a single paragraph only.
|
||||
func unwrapParagraph(html string) string {
|
||||
if !strings.HasPrefix(html, "<p>") {
|
||||
return html
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(html, "</p>") {
|
||||
return html
|
||||
}
|
||||
|
||||
// Make a substring excluding the
|
||||
// opening and closing paragraph tags.
|
||||
sub := html[3 : len(html)-4]
|
||||
|
||||
// If there are still other paragraph tags left
|
||||
// inside the substring, return html unchanged.
|
||||
containsOtherParas := parasRegexp.MatchString(sub)
|
||||
if containsOtherParas {
|
||||
return html
|
||||
}
|
||||
|
||||
// Return the substring.
|
||||
return sub
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ const (
|
|||
mdHashtagAndCodeBlockExpected = "<p><a href=\"http://localhost:8080/tags/hashtag\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>Hashtag</span></a></p><pre><code>#Hashtag\n</code></pre>"
|
||||
mdMentionAndCodeBlock = "@the_mighty_zork\n\n```\n@the_mighty_zork\n```"
|
||||
mdMentionAndCodeBlockExpected = "<p><span class=\"h-card\"><a href=\"http://localhost:8080/@the_mighty_zork\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">@<span>the_mighty_zork</span></a></span></p><pre><code>@the_mighty_zork\n</code></pre>"
|
||||
mdMentionAndCodeBlockBasicExpected = "<p>@the_mighty_zork</p><pre><code>@the_mighty_zork\n</code></pre>"
|
||||
mdWithSmartypants = "\"you have to quargle the bleepflorp\" they said with 1/2 of nominal speed and 1/3 of the usual glumping"
|
||||
mdWithSmartypantsExpected = "<p>\"you have to quargle the bleepflorp\" they said with 1/2 of nominal speed and 1/3 of the usual glumping</p>"
|
||||
mdWithAsciiHeart = "hello <3 old friend <3 i loved u </3 :(( you stole my heart"
|
||||
|
@ -76,6 +77,7 @@ const (
|
|||
mdWithStrikethroughExpected = "<p>I have <del>mdae</del> made an error</p>"
|
||||
mdWithLink = "Check out this code, i heard it was written by a sloth https://github.com/superseriousbusiness/gotosocial"
|
||||
mdWithLinkExpected = "<p>Check out this code, i heard it was written by a sloth <a href=\"https://github.com/superseriousbusiness/gotosocial\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">https://github.com/superseriousbusiness/gotosocial</a></p>"
|
||||
mdWithLinkBasicExpected = "Check out this code, i heard it was written by a sloth <a href=\"https://github.com/superseriousbusiness/gotosocial\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">https://github.com/superseriousbusiness/gotosocial</a>"
|
||||
mdObjectInCodeBlock = "@foss_satan@fossbros-anonymous.io this is how to mention a user\n```\n@the_mighty_zork hey bud! nice #ObjectOrientedProgramming software you've been writing lately! :rainbow:\n```\nhope that helps"
|
||||
mdObjectInCodeBlockExpected = "<p><span class=\"h-card\"><a href=\"http://fossbros-anonymous.io/@foss_satan\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">@<span>foss_satan</span></a></span> this is how to mention a user</p><pre><code>@the_mighty_zork hey bud! nice #ObjectOrientedProgramming software you've been writing lately! :rainbow:\n</code></pre><p>hope that helps</p>"
|
||||
// Hashtags can be italicized but only with *, not _.
|
||||
|
@ -169,6 +171,11 @@ func (suite *MarkdownTestSuite) TestParseMentionWithCodeBlock() {
|
|||
suite.Equal(mdMentionAndCodeBlockExpected, formatted.HTML)
|
||||
}
|
||||
|
||||
func (suite *MarkdownTestSuite) TestParseMentionWithCodeBlockBasic() {
|
||||
formatted := suite.FromMarkdownBasic(mdMentionAndCodeBlock)
|
||||
suite.Equal(mdMentionAndCodeBlockBasicExpected, formatted.HTML)
|
||||
}
|
||||
|
||||
func (suite *MarkdownTestSuite) TestParseSmartypants() {
|
||||
formatted := suite.FromMarkdown(mdWithSmartypants)
|
||||
suite.Equal(mdWithSmartypantsExpected, formatted.HTML)
|
||||
|
@ -189,6 +196,11 @@ func (suite *MarkdownTestSuite) TestParseLink() {
|
|||
suite.Equal(mdWithLinkExpected, formatted.HTML)
|
||||
}
|
||||
|
||||
func (suite *MarkdownTestSuite) TestParseLinkBasic() {
|
||||
formatted := suite.FromMarkdownBasic(mdWithLink)
|
||||
suite.Equal(mdWithLinkBasicExpected, formatted.HTML)
|
||||
}
|
||||
|
||||
func (suite *MarkdownTestSuite) TestParseObjectInCodeBlock() {
|
||||
formatted := suite.FromMarkdown(mdObjectInCodeBlock)
|
||||
suite.Equal(mdObjectInCodeBlockExpected, formatted.HTML)
|
||||
|
|
|
@ -20,8 +20,11 @@ package text
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
gohtml "html"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
"github.com/k3a/html2text"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/regexes"
|
||||
|
@ -52,7 +55,7 @@ func (f *Formatter) FromPlain(
|
|||
return f.fromPlain(
|
||||
ctx,
|
||||
plainTextParser,
|
||||
false, // emojiOnly = false
|
||||
false, // basic = false
|
||||
parseMention,
|
||||
authorID,
|
||||
statusID,
|
||||
|
@ -85,7 +88,7 @@ func (f *Formatter) FromPlainNoParagraph(
|
|||
return f.fromPlain(
|
||||
ctx,
|
||||
plainTextParser,
|
||||
false, // emojiOnly = false
|
||||
false, // basic = false
|
||||
parseMention,
|
||||
authorID,
|
||||
statusID,
|
||||
|
@ -93,12 +96,14 @@ func (f *Formatter) FromPlainNoParagraph(
|
|||
)
|
||||
}
|
||||
|
||||
// FromPlainEmojiOnly fulfils FormatFunc by parsing
|
||||
// FromPlainBasic fulfils FormatFunc by parsing
|
||||
// the given plaintext input into a FormatResult.
|
||||
//
|
||||
// Unlike FromPlain, it will only parse emojis with
|
||||
// the custom renderer, leaving aside mentions and tags.
|
||||
func (f *Formatter) FromPlainEmojiOnly(
|
||||
//
|
||||
// Resulting HTML will also NOT be wrapped in <p> tags.
|
||||
func (f *Formatter) FromPlainBasic(
|
||||
ctx context.Context,
|
||||
parseMention gtsmodel.ParseMentionFunc,
|
||||
authorID string,
|
||||
|
@ -116,7 +121,7 @@ func (f *Formatter) FromPlainEmojiOnly(
|
|||
return f.fromPlain(
|
||||
ctx,
|
||||
plainTextParser,
|
||||
true, // emojiOnly = true
|
||||
true, // basic = true
|
||||
parseMention,
|
||||
authorID,
|
||||
statusID,
|
||||
|
@ -130,7 +135,7 @@ func (f *Formatter) FromPlainEmojiOnly(
|
|||
func (f *Formatter) fromPlain(
|
||||
ctx context.Context,
|
||||
plainTextParser parser.Parser,
|
||||
emojiOnly bool,
|
||||
basic bool,
|
||||
parseMention gtsmodel.ParseMentionFunc,
|
||||
authorID string,
|
||||
statusID string,
|
||||
|
@ -156,7 +161,9 @@ func (f *Formatter) fromPlain(
|
|||
parseMention,
|
||||
authorID,
|
||||
statusID,
|
||||
emojiOnly,
|
||||
// If basic, pass
|
||||
// emojiOnly = true.
|
||||
basic,
|
||||
result,
|
||||
},
|
||||
// Turns URLs into links.
|
||||
|
@ -181,8 +188,51 @@ func (f *Formatter) fromPlain(
|
|||
|
||||
// Clean and shrink HTML.
|
||||
result.HTML = byteutil.B2S(htmlBytes.Bytes())
|
||||
result.HTML = SanitizeToHTML(result.HTML)
|
||||
result.HTML = SanitizeHTML(result.HTML)
|
||||
result.HTML = MinifyHTML(result.HTML)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ParseHTMLToPlain parses the given HTML string, then
|
||||
// outputs it to equivalent plaintext while trying to
|
||||
// keep as much of the smenantic intent of the input
|
||||
// HTML as possible, ie., titles are placed on separate
|
||||
// lines, `<br>`s are converted to newlines, text inside
|
||||
// `<strong>` and `<em>` tags is retained, but without
|
||||
// emphasis, `<a>` links are unnested and the URL they
|
||||
// link to is placed in angle brackets next to them,
|
||||
// lists are replaced with newline-separated indented
|
||||
// items, etc.
|
||||
//
|
||||
// This function is useful when you need to filter on
|
||||
// HTML and want to avoid catching tags in the filter,
|
||||
// or when you want to serve something in a plaintext
|
||||
// format that may contain HTML tags (eg., CWs).
|
||||
func ParseHTMLToPlain(html string) string {
|
||||
plain := html2text.HTML2TextWithOptions(
|
||||
html,
|
||||
html2text.WithLinksInnerText(),
|
||||
html2text.WithUnixLineBreaks(),
|
||||
html2text.WithListSupport(),
|
||||
)
|
||||
return strings.TrimSpace(plain)
|
||||
}
|
||||
|
||||
// StripHTMLFromText runs text through strict sanitization
|
||||
// to completely remove any HTML from the input without
|
||||
// trying to preserve the semantic intent of any HTML tags.
|
||||
//
|
||||
// This is useful in cases where the input was not allowed
|
||||
// to contain HTML at all, and the output isn't either.
|
||||
func StripHTMLFromText(text string) string {
|
||||
// Unescape first to catch any tricky critters.
|
||||
content := gohtml.UnescapeString(text)
|
||||
|
||||
// Remove all detected HTML.
|
||||
content = strict.Sanitize(content)
|
||||
|
||||
// Unescape again to return plaintext.
|
||||
content = gohtml.UnescapeString(content)
|
||||
return strings.TrimSpace(content)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/text"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -183,6 +184,150 @@ func (suite *PlainTestSuite) TestNumbersAreNotHashtags() {
|
|||
suite.Len(f.Tags, 0)
|
||||
}
|
||||
|
||||
func (suite *PlainTestSuite) TestParseHTMLToPlain() {
|
||||
for _, t := range []struct {
|
||||
html string
|
||||
expectedPlain string
|
||||
}{
|
||||
{
|
||||
// Check newlines between paras preserved.
|
||||
html: "<p>butting into a serious discussion about programming languages*: \"elixir? I barely know 'er! honk honk!\"</p><p><small>*insofar as any discussion about programming languages can truly be considered \"serious\" since programmers are fucking clowns</small></p>",
|
||||
expectedPlain: `butting into a serious discussion about programming languages*: "elixir? I barely know 'er! honk honk!"
|
||||
|
||||
*insofar as any discussion about programming languages can truly be considered "serious" since programmers are fucking clowns`,
|
||||
},
|
||||
{
|
||||
// This one looks a bit wacky but nobody should
|
||||
// be putting definition lists in summaries *really*.
|
||||
html: "<dl class=\"status-stats\"><div class=\"stats-grouping\"><div class=\"stats-item published-at text-cutoff\"><dt class=\"sr-only\">Published</dt><dd><time class=\"dt-published\" datetime=\"2025-01-15T23:49:59.299Z\">Jan 16, 2025, 00:49</time></dd></div><div class=\"stats-grouping\"><div class=\"stats-item\" title=\"Replies\"><dt><span class=\"sr-only\">Replies</span><i class=\"fa fa-reply-all\" aria-hidden=\"true\"></i></dt><dd>0</dd></div><div class=\"stats-item\" title=\"Faves\"><dt><span class=\"sr-only\">Favourites</span><i class=\"fa fa-star\" aria-hidden=\"true\"></i></dt><dd>4</dd></div><div class=\"stats-item\" title=\"Boosts\"><dt><span class=\"sr-only\">Reblogs</span><i class=\"fa fa-retweet\" aria-hidden=\"true\"></i></dt><dd>0</dd></div></div></div><div class=\"stats-item language\" title=\"English\"><dt class=\"sr-only\">Language</dt><dd><span class=\"sr-only\">English</span><span aria-hidden=\"true\">en</span></dd></div></dl>",
|
||||
expectedPlain: `PublishedJan 16, 2025, 00:49Replies0Favourites4Reblogs0LanguageEnglishen`,
|
||||
},
|
||||
{
|
||||
// Check <br> converted to newlines and leading / trailing space removed.
|
||||
html: " <p>i'm a milf,<br>i'm a lover,<br>do your mom,<br>do your brother</p><p>i'm a sinner,<br>i'm a saint,<br>i will not be ashamed!</p><br> <br>",
|
||||
expectedPlain: `i'm a milf,
|
||||
i'm a lover,
|
||||
do your mom,
|
||||
do your brother
|
||||
|
||||
i'm a sinner,
|
||||
i'm a saint,
|
||||
i will not be ashamed!`,
|
||||
},
|
||||
{
|
||||
// Check newlines, links, lists still more or less readable as such.
|
||||
html: "<p>Hello everyone, after a week or two down the release candidate mines, we've emerged blinking into the light carrying with us <a href=\"https://gts.superseriousbusiness.org/tags/gotosocial\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>GoToSocial</span></a> <strong>v0.18.0 Scroingly Sloth</strong>!</p><p><a href=\"https://github.com/superseriousbusiness/gotosocial/releases/tag/v0.18.0\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">https://github.com/superseriousbusiness/gotosocial/releases/tag/v0.18.0</a></p><p>Please read the migration notes carefully for instructions on how to upgrade to this version. <strong>This version contains several very long migrations so you will need to be patient when upgrading, and backup your database first!!</strong></p><p><strong>Release highlights</strong></p><ul><li><strong>Status edit support</strong>: one of our most-requested features! You can now edit your own statuses, and see instance edit history from other accounts too (if your instance has them stored).</li><li><strong>Push notifications</strong>: probably the second most-requested feature! GoToSocial can now send push notifications to clients via their configured push providers.<br>You may need to uninstall / reinstall client applications, or log out and back in again, for this feature to work. (And if you're using Tusky, <a href=\"https://tusky.app/faq/#why-are-notifications-less-frequent-with-tusky\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">make sure you've got ntfy installed</a>).</li><li><strong>Global instance css customization</strong>: admins can now apply custom CSS across their entire instance via the settings panel.</li><li><strong>Domain permission subscriptions</strong>: it's now possible to configure your instance to subscribe to CSV, JSON, or plaintext lists of domain permissions.<br>Each night, your instance will fetch and automatically create domain permissions (or permission drafts) based on what it finds in a subscribed list.<br>See the <a href=\"https://docs.gotosocial.org/en/latest/admin/domain_permission_subscriptions/\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">domain permission subscription documentation</a> for more information.</li><li><strong>Trusted-proxies helper</strong>: instances with improperly configured trusted-proxies settings will now show a warning on the homepage, so admins can make sure their instance is configured correctly. Check your own instance homepage after updating to see if you need to do anything.</li><li><strong>Better outbox sorting</strong>: messages from GoToSocial are now delivered more quickly to people you mention, so conversations across instances should feel a bit snappier.</li><li><strong>Log in button</strong>: there's now a login button in the top right of the instance homepage, which leads to a helpful page about clients, with a link to the settings panel. Should make things less confusing for new users!</li><li><strong>Granular stats controls</strong>: with the <code>instance-stats-mode</code> setting, admins can now choose if and how their instance serves stats via the nodeinfo endpoints. Existing behavior from v0.17.0 is the default.</li><li><strong>Post backdating</strong>: via the API you can now backdate posts (if enabled in config.yaml). This is our first step towards making it possible to import your post history from elsewhere into your GoToSocial instance. While there's no way to do this in the settings panel yet, you can already use third-party tools like Slurp to import posts from a Mastodon export (see <a href=\"https://github.com/VyrCossont/slurp\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">Slurp</a>).</li><li><strong>Configurable sign-up limits</strong>: you can now configure your sign-up backlog length and sign-up throttling (defaults remain the same).</li><li><strong>NetBSD and FreeBSD builds</strong>: yep!</li><li><strong>Respect users <code>prefers-color-scheme</code> preference</strong>: there's now a light mode default theme to complement our trusty dark mode theme, and the theme will switch based on a visitor's <code>prefers-color-scheme</code> configuration. This applies to all page and profiles, with the exception of some custom themes. Works in the settings panel too!</li></ul><p>Thanks for reading! And seriously back up your database.</p>",
|
||||
expectedPlain: `Hello everyone, after a week or two down the release candidate mines, we've emerged blinking into the light carrying with us #GoToSocial <https://gts.superseriousbusiness.org/tags/gotosocial> v0.18.0 Scroingly Sloth!
|
||||
|
||||
https://github.com/superseriousbusiness/gotosocial/releases/tag/v0.18.0 <https://github.com/superseriousbusiness/gotosocial/releases/tag/v0.18.0>
|
||||
|
||||
Please read the migration notes carefully for instructions on how to upgrade to this version. This version contains several very long migrations so you will need to be patient when upgrading, and backup your database first!!
|
||||
|
||||
Release highlights
|
||||
|
||||
|
||||
- Status edit support: one of our most-requested features! You can now edit your own statuses, and see instance edit history from other accounts too (if your instance has them stored).
|
||||
- Push notifications: probably the second most-requested feature! GoToSocial can now send push notifications to clients via their configured push providers.
|
||||
You may need to uninstall / reinstall client applications, or log out and back in again, for this feature to work. (And if you're using Tusky, make sure you've got ntfy installed <https://tusky.app/faq/#why-are-notifications-less-frequent-with-tusky>).
|
||||
- Global instance css customization: admins can now apply custom CSS across their entire instance via the settings panel.
|
||||
- Domain permission subscriptions: it's now possible to configure your instance to subscribe to CSV, JSON, or plaintext lists of domain permissions.
|
||||
Each night, your instance will fetch and automatically create domain permissions (or permission drafts) based on what it finds in a subscribed list.
|
||||
See the domain permission subscription documentation <https://docs.gotosocial.org/en/latest/admin/domain_permission_subscriptions/> for more information.
|
||||
- Trusted-proxies helper: instances with improperly configured trusted-proxies settings will now show a warning on the homepage, so admins can make sure their instance is configured correctly. Check your own instance homepage after updating to see if you need to do anything.
|
||||
- Better outbox sorting: messages from GoToSocial are now delivered more quickly to people you mention, so conversations across instances should feel a bit snappier.
|
||||
- Log in button: there's now a login button in the top right of the instance homepage, which leads to a helpful page about clients, with a link to the settings panel. Should make things less confusing for new users!
|
||||
- Granular stats controls: with the instance-stats-mode setting, admins can now choose if and how their instance serves stats via the nodeinfo endpoints. Existing behavior from v0.17.0 is the default.
|
||||
- Post backdating: via the API you can now backdate posts (if enabled in config.yaml). This is our first step towards making it possible to import your post history from elsewhere into your GoToSocial instance. While there's no way to do this in the settings panel yet, you can already use third-party tools like Slurp to import posts from a Mastodon export (see Slurp <https://github.com/VyrCossont/slurp>).
|
||||
- Configurable sign-up limits: you can now configure your sign-up backlog length and sign-up throttling (defaults remain the same).
|
||||
- NetBSD and FreeBSD builds: yep!
|
||||
- Respect users prefers-color-scheme preference: there's now a light mode default theme to complement our trusty dark mode theme, and the theme will switch based on a visitor's prefers-color-scheme configuration. This applies to all page and profiles, with the exception of some custom themes. Works in the settings panel too!
|
||||
|
||||
|
||||
Thanks for reading! And seriously back up your database.`,
|
||||
},
|
||||
} {
|
||||
plain := text.ParseHTMLToPlain(t.html)
|
||||
suite.Equal(t.expectedPlain, plain)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *PlainTestSuite) TestStripCaption1() {
|
||||
dodgyCaption := "<script>console.log('haha!')</script>this is just a normal caption ;)"
|
||||
stripped := text.StripHTMLFromText(dodgyCaption)
|
||||
suite.Equal("this is just a normal caption ;)", stripped)
|
||||
}
|
||||
|
||||
func (suite *PlainTestSuite) TestStripCaption2() {
|
||||
dodgyCaption := "<em>here's a LOUD caption</em>"
|
||||
stripped := text.StripHTMLFromText(dodgyCaption)
|
||||
suite.Equal("here's a LOUD caption", stripped)
|
||||
}
|
||||
|
||||
func (suite *PlainTestSuite) TestStripCaption3() {
|
||||
dodgyCaption := ""
|
||||
stripped := text.StripHTMLFromText(dodgyCaption)
|
||||
suite.Equal("", stripped)
|
||||
}
|
||||
|
||||
func (suite *PlainTestSuite) TestStripCaption4() {
|
||||
dodgyCaption := `
|
||||
|
||||
|
||||
here is
|
||||
a multi line
|
||||
caption
|
||||
with some newlines
|
||||
|
||||
|
||||
|
||||
`
|
||||
stripped := text.StripHTMLFromText(dodgyCaption)
|
||||
suite.Equal("here is\na multi line\ncaption\nwith some newlines", stripped)
|
||||
}
|
||||
|
||||
func (suite *PlainTestSuite) TestStripCaption5() {
|
||||
// html-escaped: "<script>console.log('aha!')</script> hello world"
|
||||
dodgyCaption := `<script>console.log('aha!')</script> hello world`
|
||||
stripped := text.StripHTMLFromText(dodgyCaption)
|
||||
suite.Equal("hello world", stripped)
|
||||
}
|
||||
|
||||
func (suite *PlainTestSuite) TestStripCaption6() {
|
||||
// html-encoded: "<script>console.log('aha!')</script> hello world"
|
||||
dodgyCaption := `<script>console.log('aha!')</script> hello world`
|
||||
stripped := text.StripHTMLFromText(dodgyCaption)
|
||||
suite.Equal("hello world", stripped)
|
||||
}
|
||||
|
||||
func (suite *PlainTestSuite) TestStripCustomCSS() {
|
||||
customCSS := `.toot .username {
|
||||
color: var(--link_fg);
|
||||
line-height: 2rem;
|
||||
margin-top: -0.5rem;
|
||||
align-self: start;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}`
|
||||
stripped := text.StripHTMLFromText(customCSS)
|
||||
suite.Equal(customCSS, stripped) // should be the same as it was before
|
||||
}
|
||||
|
||||
func (suite *PlainTestSuite) TestStripNaughtyCustomCSS1() {
|
||||
// try to break out of <style> into <head> and change the document title
|
||||
customCSS := "</style><title>pee pee poo poo</title><style>"
|
||||
stripped := text.StripHTMLFromText(customCSS)
|
||||
suite.Empty(stripped)
|
||||
}
|
||||
|
||||
func (suite *PlainTestSuite) TestStripNaughtyCustomCSS2() {
|
||||
// try to break out of <style> into <head> and change the document title
|
||||
customCSS := "pee pee poo poo</style><title></title><style>"
|
||||
stripped := text.StripHTMLFromText(customCSS)
|
||||
suite.Equal("pee pee poo poo", stripped)
|
||||
}
|
||||
|
||||
func TestPlainTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(PlainTestSuite))
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package text
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const (
|
||||
test_removeHTML = `<p>Another test <span class="h-card"><a href="http://fossbros-anonymous.io/@foss_satan" class="u-url mention" rel="nofollow noreferrer noopener" target="_blank">@<span>foss_satan</span></a></span><br/><br/><a href="http://localhost:8080/tags/Hashtag" class="mention hashtag" rel="tag nofollow noreferrer noopener" target="_blank">#<span>Hashtag</span></a><br/><br/>Text</p>`
|
||||
test_removedHTML = `Another test @foss_satan#HashtagText`
|
||||
test_withEscapedLiteral = `it\u0026amp;#39;s its it is`
|
||||
test_withEscapedLiteralExpected = `it\u0026amp;#39;s its it is`
|
||||
test_withEscaped = "it\u0026amp;#39;s its it is"
|
||||
test_withEscapedExpected = "it&#39;s its it is"
|
||||
)
|
||||
|
||||
type RemoveHTMLTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *RemoveHTMLTestSuite) TestSanitizeWithEscapedLiteral() {
|
||||
s := removeHTML(test_withEscapedLiteral)
|
||||
suite.Equal(test_withEscapedLiteralExpected, s)
|
||||
}
|
||||
|
||||
func (suite *RemoveHTMLTestSuite) TestSanitizeWithEscaped() {
|
||||
s := removeHTML(test_withEscaped)
|
||||
suite.Equal(test_withEscapedExpected, s)
|
||||
}
|
||||
|
||||
func (suite *RemoveHTMLTestSuite) TestRemoveHTML() {
|
||||
s := removeHTML(test_removeHTML)
|
||||
suite.Equal(test_removedHTML, s)
|
||||
}
|
||||
|
||||
func TestRemoveHTMLTestSuite(t *testing.T) {
|
||||
suite.Run(t, &RemoveHTMLTestSuite{})
|
||||
}
|
|
@ -18,9 +18,7 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"html"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
)
|
||||
|
@ -163,29 +161,10 @@ var regular *bluemonday.Policy = func() *bluemonday.Policy {
|
|||
// Source: https://github.com/microcosm-cc/bluemonday#usage
|
||||
var strict *bluemonday.Policy = bluemonday.StrictPolicy()
|
||||
|
||||
// removeHTML strictly removes *all* recognized
|
||||
// HTML elements from the given string.
|
||||
func removeHTML(in string) string {
|
||||
return strict.Sanitize(in)
|
||||
}
|
||||
|
||||
// SanitizeToHTML sanitizes only risky html elements
|
||||
// SanitizeHTML sanitizes only risky html elements
|
||||
// from the given string, allowing safe ones through.
|
||||
func SanitizeToHTML(in string) string {
|
||||
return regular.Sanitize(in)
|
||||
}
|
||||
|
||||
// SanitizeToPlaintext runs text through basic sanitization.
|
||||
// This removes any html elements that were in the string,
|
||||
// and returns clean plaintext.
|
||||
func SanitizeToPlaintext(in string) string {
|
||||
// Unescape first to catch any tricky critters.
|
||||
content := html.UnescapeString(in)
|
||||
|
||||
// Remove all detected HTML.
|
||||
content = removeHTML(content)
|
||||
|
||||
// Unescape again to return plaintext.
|
||||
content = html.UnescapeString(content)
|
||||
return strings.TrimSpace(content)
|
||||
//
|
||||
// It returns an HTML string.
|
||||
func SanitizeHTML(html string) string {
|
||||
return regular.Sanitize(html)
|
||||
}
|
||||
|
|
|
@ -36,95 +36,18 @@ type SanitizeTestSuite struct {
|
|||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeOutgoing() {
|
||||
s := text.SanitizeToHTML(sanitizeOutgoing)
|
||||
s := text.SanitizeHTML(sanitizeOutgoing)
|
||||
suite.Equal(sanitizedOutgoing, s)
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeHTML() {
|
||||
s := text.SanitizeToHTML(sanitizeHTML)
|
||||
s := text.SanitizeHTML(sanitizeHTML)
|
||||
suite.Equal(sanitizedHTML, s)
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeCaption1() {
|
||||
dodgyCaption := "<script>console.log('haha!')</script>this is just a normal caption ;)"
|
||||
sanitized := text.SanitizeToPlaintext(dodgyCaption)
|
||||
suite.Equal("this is just a normal caption ;)", sanitized)
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeCaption2() {
|
||||
dodgyCaption := "<em>here's a LOUD caption</em>"
|
||||
sanitized := text.SanitizeToPlaintext(dodgyCaption)
|
||||
suite.Equal("here's a LOUD caption", sanitized)
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeCaption3() {
|
||||
dodgyCaption := ""
|
||||
sanitized := text.SanitizeToPlaintext(dodgyCaption)
|
||||
suite.Equal("", sanitized)
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeCaption4() {
|
||||
dodgyCaption := `
|
||||
|
||||
|
||||
here is
|
||||
a multi line
|
||||
caption
|
||||
with some newlines
|
||||
|
||||
|
||||
|
||||
`
|
||||
sanitized := text.SanitizeToPlaintext(dodgyCaption)
|
||||
suite.Equal("here is\na multi line\ncaption\nwith some newlines", sanitized)
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeCaption5() {
|
||||
// html-escaped: "<script>console.log('aha!')</script> hello world"
|
||||
dodgyCaption := `<script>console.log('aha!')</script> hello world`
|
||||
sanitized := text.SanitizeToPlaintext(dodgyCaption)
|
||||
suite.Equal("hello world", sanitized)
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeCaption6() {
|
||||
// html-encoded: "<script>console.log('aha!')</script> hello world"
|
||||
dodgyCaption := `<script>console.log('aha!')</script> hello world`
|
||||
sanitized := text.SanitizeToPlaintext(dodgyCaption)
|
||||
suite.Equal("hello world", sanitized)
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeCustomCSS() {
|
||||
customCSS := `.toot .username {
|
||||
color: var(--link_fg);
|
||||
line-height: 2rem;
|
||||
margin-top: -0.5rem;
|
||||
align-self: start;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}`
|
||||
sanitized := text.SanitizeToPlaintext(customCSS)
|
||||
suite.Equal(customCSS, sanitized) // should be the same as it was before
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeNaughtyCustomCSS1() {
|
||||
// try to break out of <style> into <head> and change the document title
|
||||
customCSS := "</style><title>pee pee poo poo</title><style>"
|
||||
sanitized := text.SanitizeToPlaintext(customCSS)
|
||||
suite.Empty(sanitized)
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeNaughtyCustomCSS2() {
|
||||
// try to break out of <style> into <head> and change the document title
|
||||
customCSS := "pee pee poo poo</style><title></title><style>"
|
||||
sanitized := text.SanitizeToPlaintext(customCSS)
|
||||
suite.Equal("pee pee poo poo", sanitized)
|
||||
}
|
||||
|
||||
func (suite *SanitizeTestSuite) TestSanitizeInlineImg() {
|
||||
withInlineImg := "<p>Here's an inline image: <img class=\"fixed-size-img svelte-uci8eb\" aria-hidden=\"false\" alt=\"A black-and-white photo of an Oblique Strategy card. The card reads: 'Define an area as 'safe' and use it as an anchor'.\" title=\"A black-and-white photo of an Oblique Strategy card. The card reads: 'Define an area as 'safe' and use it as an anchor'.\" width=\"0\" height=\"0\" src=\"https://example.org/fileserver/01H7J83147QMCE17C0RS9P10Y9/attachment/small/01H7J8365XXRTCP6CAMGEM49ZE.jpg\" style=\"object-position: 50% 50%;\"></p>"
|
||||
sanitized := text.SanitizeToHTML(withInlineImg)
|
||||
sanitized := text.SanitizeHTML(withInlineImg)
|
||||
suite.Equal(`<p>Here's an inline image: </p>`, sanitized)
|
||||
}
|
||||
|
||||
|
|
|
@ -531,9 +531,9 @@ func (suite *InternalToASTestSuite) TestStatusToAS() {
|
|||
"attachment": [],
|
||||
"attributedTo": "http://localhost:8080/users/the_mighty_zork",
|
||||
"cc": "http://localhost:8080/users/the_mighty_zork/followers",
|
||||
"content": "hello everyone!",
|
||||
"content": "\u003cp\u003ehello everyone!\u003c/p\u003e",
|
||||
"contentMap": {
|
||||
"en": "hello everyone!"
|
||||
"en": "\u003cp\u003ehello everyone!\u003c/p\u003e"
|
||||
},
|
||||
"id": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
"interactionPolicy": {
|
||||
|
@ -613,9 +613,9 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASWithIDs() {
|
|||
],
|
||||
"attributedTo": "http://localhost:8080/users/admin",
|
||||
"cc": "http://localhost:8080/users/admin/followers",
|
||||
"content": "hello world! #welcome ! first post on the instance :rainbow: !",
|
||||
"content": "\u003cp\u003ehello world! \u003ca href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\"\u003e#\u003cspan\u003ewelcome\u003c/span\u003e\u003c/a\u003e ! first post on the instance :rainbow: !\u003c/p\u003e",
|
||||
"contentMap": {
|
||||
"en": "hello world! #welcome ! first post on the instance :rainbow: !"
|
||||
"en": "\u003cp\u003ehello world! \u003ca href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\"\u003e#\u003cspan\u003ewelcome\u003c/span\u003e\u003c/a\u003e ! first post on the instance :rainbow: !\u003c/p\u003e"
|
||||
},
|
||||
"id": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
"interactionPolicy": {
|
||||
|
@ -713,9 +713,9 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASFromDB() {
|
|||
],
|
||||
"attributedTo": "http://localhost:8080/users/admin",
|
||||
"cc": "http://localhost:8080/users/admin/followers",
|
||||
"content": "hello world! #welcome ! first post on the instance :rainbow: !",
|
||||
"content": "\u003cp\u003ehello world! \u003ca href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\"\u003e#\u003cspan\u003ewelcome\u003c/span\u003e\u003c/a\u003e ! first post on the instance :rainbow: !\u003c/p\u003e",
|
||||
"contentMap": {
|
||||
"en": "hello world! #welcome ! first post on the instance :rainbow: !"
|
||||
"en": "\u003cp\u003ehello world! \u003ca href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\"\u003e#\u003cspan\u003ewelcome\u003c/span\u003e\u003c/a\u003e ! first post on the instance :rainbow: !\u003c/p\u003e"
|
||||
},
|
||||
"id": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
"interactionPolicy": {
|
||||
|
@ -805,9 +805,9 @@ func (suite *InternalToASTestSuite) TestStatusToASWithMentions() {
|
|||
"http://localhost:8080/users/admin/followers",
|
||||
"http://localhost:8080/users/the_mighty_zork"
|
||||
],
|
||||
"content": "hi @the_mighty_zork welcome to the instance!",
|
||||
"content": "\u003cp\u003ehi \u003cspan class=\"h-card\"\u003e\u003ca href=\"http://localhost:8080/@1happyturtle\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\"\u003e@\u003cspan\u003ethe_mighty_zork\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e welcome to the instance!\u003c/p\u003e",
|
||||
"contentMap": {
|
||||
"en": "hi @the_mighty_zork welcome to the instance!"
|
||||
"en": "\u003cp\u003ehi \u003cspan class=\"h-card\"\u003e\u003ca href=\"http://localhost:8080/@1happyturtle\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\"\u003e@\u003cspan\u003ethe_mighty_zork\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e welcome to the instance!\u003c/p\u003e"
|
||||
},
|
||||
"id": "http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
|
||||
"inReplyTo": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
|
|
|
@ -40,6 +40,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/language"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/text"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/uris"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
@ -1126,12 +1127,15 @@ func (c *Converter) StatusToWebStatus(
|
|||
|
||||
webStatus := &apimodel.WebStatus{
|
||||
Status: apiStatus,
|
||||
SpoilerContent: s.ContentWarning,
|
||||
Account: acct,
|
||||
}
|
||||
|
||||
// Whack a newline before and after each "pre" to make it easier to outdent it.
|
||||
webStatus.Content = strings.ReplaceAll(webStatus.Content, "<pre>", "\n<pre>")
|
||||
webStatus.Content = strings.ReplaceAll(webStatus.Content, "</pre>", "</pre>\n")
|
||||
webStatus.SpoilerContent = strings.ReplaceAll(webStatus.SpoilerContent, "<pre>", "\n<pre>")
|
||||
webStatus.SpoilerContent = strings.ReplaceAll(webStatus.SpoilerContent, "</pre>", "</pre>\n")
|
||||
|
||||
// Add additional information for template.
|
||||
// Assume empty langs, hope for not empty language.
|
||||
|
@ -1372,7 +1376,6 @@ func (c *Converter) baseStatusToFrontend(
|
|||
InReplyToID: nil, // Set below.
|
||||
InReplyToAccountID: nil, // Set below.
|
||||
Sensitive: *s.Sensitive,
|
||||
SpoilerText: s.ContentWarning,
|
||||
Visibility: c.VisToAPIVis(ctx, s.Visibility),
|
||||
LocalOnly: s.IsLocalOnly(),
|
||||
Language: nil, // Set below.
|
||||
|
@ -1393,6 +1396,11 @@ func (c *Converter) baseStatusToFrontend(
|
|||
Text: s.Text,
|
||||
ContentType: ContentTypeToAPIContentType(s.ContentType),
|
||||
InteractionPolicy: *apiInteractionPolicy,
|
||||
|
||||
// Mastodon API says spoiler_text should be *text*, not HTML, so
|
||||
// parse any HTML back to plaintext when serializing via the API,
|
||||
// attempting to preserve semantic intent to keep it readable.
|
||||
SpoilerText: text.ParseHTMLToPlain(s.ContentWarning),
|
||||
}
|
||||
|
||||
if at := s.EditedAt; !at.IsZero() {
|
||||
|
|
|
@ -490,7 +490,155 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() {
|
|||
"muted": false,
|
||||
"bookmarked": true,
|
||||
"pinned": false,
|
||||
"content": "hello world! #welcome ! first post on the instance :rainbow: !",
|
||||
"content": "\u003cp\u003ehello world! \u003ca href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\"\u003e#\u003cspan\u003ewelcome\u003c/span\u003e\u003c/a\u003e ! first post on the instance :rainbow: !\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "superseriousbusiness",
|
||||
"website": "https://superserious.business"
|
||||
},
|
||||
"account": {
|
||||
"id": "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
"username": "admin",
|
||||
"acct": "admin",
|
||||
"display_name": "",
|
||||
"locked": false,
|
||||
"discoverable": true,
|
||||
"bot": false,
|
||||
"created_at": "2022-05-17T13:10:59.000Z",
|
||||
"note": "",
|
||||
"url": "http://localhost:8080/@admin",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.webp",
|
||||
"header_static": "http://localhost:8080/assets/default_header.webp",
|
||||
"header_description": "Flat gray background (default header).",
|
||||
"followers_count": 1,
|
||||
"following_count": 1,
|
||||
"statuses_count": 4,
|
||||
"last_status_at": "2021-10-20",
|
||||
"emojis": [],
|
||||
"fields": [],
|
||||
"enable_rss": true,
|
||||
"roles": [
|
||||
{
|
||||
"id": "admin",
|
||||
"name": "admin",
|
||||
"color": ""
|
||||
}
|
||||
],
|
||||
"group": false
|
||||
},
|
||||
"media_attachments": [
|
||||
{
|
||||
"id": "01F8MH6NEM8D7527KZAECTCR76",
|
||||
"type": "image",
|
||||
"url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg",
|
||||
"text_url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg",
|
||||
"preview_url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.webp",
|
||||
"remote_url": null,
|
||||
"preview_remote_url": null,
|
||||
"meta": {
|
||||
"original": {
|
||||
"width": 1200,
|
||||
"height": 630,
|
||||
"size": "1200x630",
|
||||
"aspect": 1.9047619
|
||||
},
|
||||
"small": {
|
||||
"width": 512,
|
||||
"height": 268,
|
||||
"size": "512x268",
|
||||
"aspect": 1.9104477
|
||||
},
|
||||
"focus": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"description": "Black and white image of some 50's style text saying: Welcome On Board",
|
||||
"blurhash": "LIIE|gRj00WB-;j[t7j[4nWBj[Rj"
|
||||
}
|
||||
],
|
||||
"mentions": [],
|
||||
"tags": [
|
||||
{
|
||||
"name": "welcome",
|
||||
"url": "http://localhost:8080/tags/welcome"
|
||||
}
|
||||
],
|
||||
"emojis": [
|
||||
{
|
||||
"shortcode": "rainbow",
|
||||
"url": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png",
|
||||
"static_url": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png",
|
||||
"visible_in_picker": true,
|
||||
"category": "reactions"
|
||||
}
|
||||
],
|
||||
"card": null,
|
||||
"poll": null,
|
||||
"text": "hello world! #welcome ! first post on the instance :rainbow: !",
|
||||
"content_type": "text/plain",
|
||||
"interaction_policy": {
|
||||
"can_favourite": {
|
||||
"always": [
|
||||
"public",
|
||||
"me"
|
||||
],
|
||||
"with_approval": []
|
||||
},
|
||||
"can_reply": {
|
||||
"always": [
|
||||
"public",
|
||||
"me"
|
||||
],
|
||||
"with_approval": []
|
||||
},
|
||||
"can_reblog": {
|
||||
"always": [
|
||||
"public",
|
||||
"me"
|
||||
],
|
||||
"with_approval": []
|
||||
}
|
||||
}
|
||||
}`, string(b))
|
||||
}
|
||||
|
||||
func (suite *InternalToFrontendTestSuite) TestStatusToFrontendHTMLContentWarning() {
|
||||
// Change status content warning.
|
||||
testStatus := new(gtsmodel.Status)
|
||||
*testStatus = *suite.testStatuses["admin_account_status_1"]
|
||||
testStatus.ContentWarning = `<p>First paragraph of content warning</p><h4>Here's the title!</h4><p></p><p>Big boobs<br>Tee hee!<br><br>Some more text<br>And a bunch more<br><br>Hasta la victoria siempre!</p>`
|
||||
|
||||
requestingAccount := suite.testAccounts["local_account_1"]
|
||||
apiStatus, err := suite.typeconverter.StatusToAPIStatus(context.Background(), testStatus, requestingAccount, statusfilter.FilterContextNone, nil, nil)
|
||||
suite.NoError(err)
|
||||
|
||||
b, err := json.MarshalIndent(apiStatus, "", " ")
|
||||
suite.NoError(err)
|
||||
|
||||
suite.Equal(`{
|
||||
"id": "01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
"created_at": "2021-10-20T11:36:45.000Z",
|
||||
"edited_at": null,
|
||||
"in_reply_to_id": null,
|
||||
"in_reply_to_account_id": null,
|
||||
"sensitive": false,
|
||||
"spoiler_text": "First paragraph of content warning\n\nHere's the title!\n\nBig boobs\nTee hee!\n\nSome more text\nAnd a bunch more\n\nHasta la victoria siempre!",
|
||||
"visibility": "public",
|
||||
"language": "en",
|
||||
"uri": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
"url": "http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
"replies_count": 1,
|
||||
"reblogs_count": 0,
|
||||
"favourites_count": 1,
|
||||
"favourited": true,
|
||||
"reblogged": false,
|
||||
"muted": false,
|
||||
"bookmarked": true,
|
||||
"pinned": false,
|
||||
"content": "\u003cp\u003ehello world! \u003ca href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\"\u003e#\u003cspan\u003ewelcome\u003c/span\u003e\u003c/a\u003e ! first post on the instance :rainbow: !\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "superseriousbusiness",
|
||||
|
@ -671,7 +819,7 @@ func (suite *InternalToFrontendTestSuite) TestWarnFilteredStatusToFrontend() {
|
|||
"muted": false,
|
||||
"bookmarked": true,
|
||||
"pinned": false,
|
||||
"content": "hello world! #welcome ! first post on the instance :rainbow: ! fnord",
|
||||
"content": "\u003cp\u003ehello world! \u003ca href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\"\u003e#\u003cspan\u003ewelcome\u003c/span\u003e\u003c/a\u003e ! first post on the instance :rainbow: !\u003c/p\u003e fnord",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "superseriousbusiness",
|
||||
|
@ -861,7 +1009,7 @@ func (suite *InternalToFrontendTestSuite) TestWarnFilteredBoostToFrontend() {
|
|||
"muted": false,
|
||||
"bookmarked": true,
|
||||
"pinned": false,
|
||||
"content": "hello world! #welcome ! first post on the instance :rainbow: ! fnord",
|
||||
"content": "\u003cp\u003ehello world! \u003ca href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\"\u003e#\u003cspan\u003ewelcome\u003c/span\u003e\u003c/a\u003e ! first post on the instance :rainbow: !\u003c/p\u003e fnord",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "superseriousbusiness",
|
||||
|
@ -1591,7 +1739,7 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendUnknownLanguage()
|
|||
"muted": false,
|
||||
"bookmarked": true,
|
||||
"pinned": false,
|
||||
"content": "hello world! #welcome ! first post on the instance :rainbow: !",
|
||||
"content": "\u003cp\u003ehello world! \u003ca href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\"\u003e#\u003cspan\u003ewelcome\u003c/span\u003e\u003c/a\u003e ! first post on the instance :rainbow: !\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "superseriousbusiness",
|
||||
|
@ -1737,7 +1885,7 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendPartialInteraction
|
|||
"muted": false,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
|
||||
"content": "\u003cp\u003ethis is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "really cool gts application",
|
||||
|
@ -2818,7 +2966,7 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
|
|||
"muted": false,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "dark souls status bot: \"thoughts of dog\"",
|
||||
"content": "\u003cp\u003edark souls status bot: \"thoughts of dog\"\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
|
@ -3332,7 +3480,7 @@ func (suite *InternalToFrontendTestSuite) TestIntReqToAPI() {
|
|||
"muted": false,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢",
|
||||
"content": "\u003cp\u003e🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "kindaweird",
|
||||
|
@ -3599,7 +3747,7 @@ func (suite *InternalToFrontendTestSuite) TestConversationToAPISelfConvo() {
|
|||
"muted": false,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "hello everyone!",
|
||||
"content": "\u003cp\u003ehello everyone!\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "really cool gts application",
|
||||
|
@ -3769,7 +3917,7 @@ func (suite *InternalToFrontendTestSuite) TestConversationToAPI() {
|
|||
"muted": false,
|
||||
"bookmarked": false,
|
||||
"pinned": false,
|
||||
"content": "hello everyone!",
|
||||
"content": "\u003cp\u003ehello everyone!\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"application": {
|
||||
"name": "really cool gts application",
|
||||
|
|
|
@ -54,7 +54,7 @@ func (suite *InternalToRSSTestSuite) TestStatusToRSSItem1() {
|
|||
suite.Equal("", item.Enclosure.Length)
|
||||
suite.Equal("", item.Enclosure.Type)
|
||||
suite.Equal("", item.Enclosure.Url)
|
||||
suite.Equal("hello everyone!", item.Content)
|
||||
suite.Equal("<p>hello everyone!</p>", item.Content)
|
||||
}
|
||||
|
||||
func (suite *InternalToRSSTestSuite) TestStatusToRSSItem2() {
|
||||
|
@ -79,7 +79,7 @@ func (suite *InternalToRSSTestSuite) TestStatusToRSSItem2() {
|
|||
suite.Equal("62529", item.Enclosure.Length)
|
||||
suite.Equal("image/jpeg", item.Enclosure.Type)
|
||||
suite.Equal("http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg", item.Enclosure.Url)
|
||||
suite.Equal("hello world! #welcome ! first post on the instance <img src=\"http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png\" title=\":rainbow:\" alt=\":rainbow:\" width=\"25\" height=\"25\" /> !", item.Content)
|
||||
suite.Equal("<p>hello world! <a href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>welcome</span></a> ! first post on the instance <img src=\"http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png\" title=\":rainbow:\" alt=\":rainbow:\" width=\"25\" height=\"25\" /> !</p>", item.Content)
|
||||
}
|
||||
|
||||
func (suite *InternalToRSSTestSuite) TestStatusToRSSItem3() {
|
||||
|
|
|
@ -28,7 +28,6 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/k3a/html2text"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
|
@ -247,7 +246,7 @@ func systemMessage(
|
|||
wrappedNote.WriteString(`<div class="gts-system-message `)
|
||||
wrappedNote.WriteString(messageClass)
|
||||
wrappedNote.WriteString(`">`)
|
||||
wrappedNote.WriteString(text.SanitizeToHTML(unsanitizedNoteHTML))
|
||||
wrappedNote.WriteString(text.SanitizeHTML(unsanitizedNoteHTML))
|
||||
wrappedNote.WriteString(`</div>`)
|
||||
|
||||
return wrappedNote.String()
|
||||
|
@ -380,15 +379,11 @@ func filterableFields(s *gtsmodel.Status) []string {
|
|||
|
||||
// Status content. Though we have raw text
|
||||
// available for statuses created on our
|
||||
// instance, use the html2text version to
|
||||
// instance, use the plaintext version to
|
||||
// remove markdown-formatting characters
|
||||
// and ensure more consistent filtering.
|
||||
if s.Content != "" {
|
||||
text := html2text.HTML2TextWithOptions(
|
||||
s.Content,
|
||||
html2text.WithLinksInnerText(),
|
||||
html2text.WithUnixLineBreaks(),
|
||||
)
|
||||
text := text.ParseHTMLToPlain(s.Content)
|
||||
if text != "" {
|
||||
fields = append(fields, text)
|
||||
}
|
||||
|
|
|
@ -90,9 +90,9 @@ func (suite *WrapTestSuite) TestWrapNoteInCreate() {
|
|||
"attachment": [],
|
||||
"attributedTo": "http://localhost:8080/users/the_mighty_zork",
|
||||
"cc": "http://localhost:8080/users/the_mighty_zork/followers",
|
||||
"content": "hello everyone!",
|
||||
"content": "\u003cp\u003ehello everyone!\u003c/p\u003e",
|
||||
"contentMap": {
|
||||
"en": "hello everyone!"
|
||||
"en": "\u003cp\u003ehello everyone!\u003c/p\u003e"
|
||||
},
|
||||
"id": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
"interactionPolicy": {
|
||||
|
|
|
@ -361,10 +361,10 @@ func formatNotificationBody(apiNotification *apimodel.Notification) string {
|
|||
if apiNotification.Status.SpoilerText != "" {
|
||||
body = apiNotification.Status.SpoilerText
|
||||
} else {
|
||||
body = text.SanitizeToPlaintext(apiNotification.Status.Content)
|
||||
body = text.StripHTMLFromText(apiNotification.Status.Content)
|
||||
}
|
||||
} else {
|
||||
body = text.SanitizeToPlaintext(apiNotification.Account.Note)
|
||||
body = text.StripHTMLFromText(apiNotification.Account.Note)
|
||||
}
|
||||
return firstNBytesTrimSpace(body, bodyMaxLen)
|
||||
}
|
||||
|
|
|
@ -1406,22 +1406,18 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
PinnedAt: TimeMustParse("2022-05-14T13:21:09+02:00"),
|
||||
URI: "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
URL: "http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
Content: "hello world! #welcome ! first post on the instance :rainbow: !",
|
||||
Content: "<p>hello world! <a href=\"http://localhost:8080/tags/welcome\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>welcome</span></a> ! first post on the instance :rainbow: !</p>",
|
||||
Text: "hello world! #welcome ! first post on the instance :rainbow: !",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
AttachmentIDs: []string{"01F8MH6NEM8D7527KZAECTCR76"},
|
||||
TagIDs: []string{"01F8MHA1A2NF9MJ3WCCQ3K8BSZ"},
|
||||
MentionIDs: []string{},
|
||||
EmojiIDs: []string{"01F8MH9H8E4VG3KDYJR9EGPXCQ"},
|
||||
CreatedAt: TimeMustParse("2021-10-20T11:36:45Z"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWDF2Q4HV5QC161C4TGQ0M3",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
@ -1435,18 +1431,16 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
PinnedAt: TimeMustParse("2022-05-14T14:21:09+02:00"),
|
||||
URI: "http://localhost:8080/users/admin/statuses/01F8MHAAY43M6RJ473VQFCVH37",
|
||||
URL: "http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37",
|
||||
Content: "🐕🐕🐕🐕🐕",
|
||||
Content: "<p>🐕🐕🐕🐕🐕</p>",
|
||||
Text: "🐕🐕🐕🐕🐕",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:36:45Z"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWDQ1C7APSEY34B1HFVHVX7",
|
||||
ContentWarning: "open to see some puppies",
|
||||
ContentWarning: "open to see some <strong>puppies</strong>",
|
||||
ContentWarningText: "open to see some **puppies**",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(true),
|
||||
Language: "en",
|
||||
|
@ -1459,11 +1453,10 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01FF25D5Q0DH7CHD57CTRS6WK0",
|
||||
URI: "http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
|
||||
URL: "http://localhost:8080/@admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
|
||||
Content: "hi @the_mighty_zork welcome to the instance!",
|
||||
Content: "<p>hi <span class=\"h-card\"><a href=\"http://localhost:8080/@1happyturtle\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">@<span>the_mighty_zork</span></a></span> welcome to the instance!</p>",
|
||||
Text: "hi @the_mighty_zork welcome to the instance!",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
CreatedAt: TimeMustParse("2021-11-20T13:32:16Z"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
MentionIDs: []string{"01FF26A6BGEKCZFWNEHXB2ZZ6M"},
|
||||
|
@ -1471,7 +1464,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
InReplyToID: "01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
InReplyToAccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToURI: "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
|
@ -1486,16 +1478,11 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
URI: "http://localhost:8080/users/admin/statuses/01G36SF3V6Y6V5BF9P4R7PQG7G",
|
||||
URL: "http://localhost:8080/@admin/statuses/01G36SF3V6Y6V5BF9P4R7PQG7G",
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:41:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToID: "",
|
||||
InReplyToAccountID: "",
|
||||
InReplyToURI: "",
|
||||
BoostOfID: "01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
BoostOfAccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
ThreadID: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
|
||||
|
@ -1511,7 +1498,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Text: "Hi @1happyturtle, can I reply?",
|
||||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
CreatedAt: TimeMustParse("2024-02-20T12:41:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
MentionIDs: []string{"01J5QVP69ANF1K4WHES6GA4WXP"},
|
||||
|
@ -1519,8 +1505,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
InReplyToID: "01F8MHC8VWDRBQR0N1BATDDEM5",
|
||||
InReplyToAccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
InReplyToURI: "http://localhost:8080/users/1happyturtle/statuses/01F8MHC8VWDRBQR0N1BATDDEM5",
|
||||
BoostOfID: "",
|
||||
BoostOfAccountID: "",
|
||||
ThreadID: "01HCWE4P0EW9HBA5WHW97D5YV0",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
|
@ -1533,16 +1517,13 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
URI: "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
URL: "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
Content: "hello everyone!",
|
||||
Content: "<p>hello everyone!</p>",
|
||||
Text: "hello everyone!",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||
ContentWarning: "introduction post",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
|
@ -1557,18 +1538,14 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01F8MHAYFKS4KMXF8K5Y1C0KRN",
|
||||
URI: "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAYFKS4KMXF8K5Y1C0KRN",
|
||||
URL: "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAYFKS4KMXF8K5Y1C0KRN",
|
||||
Content: "this is a Public local-only post that shouldn't federate, but it's still boostable, replyable, and likeable. also it has no stored content type",
|
||||
Text: "this is a Public local-only post that shouldn't federate, but it's still boostable, replyable, and likeable. also it has no stored content type",
|
||||
Content: "<p>this is a Public local-only post that shouldn't federate, but it's still boostable, replyable, and likeable</p>",
|
||||
Text: "this is a Public local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
|
||||
ContentType: 0,
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWDVTW3HQWSX66VJQ91Z1RH",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
@ -1581,16 +1558,13 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01F8MHBBN8120SYH7D5S050MGK",
|
||||
URI: "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHBBN8120SYH7D5S050MGK",
|
||||
URL: "http://localhost:8080/@the_mighty_zork/statuses/01F8MHBBN8120SYH7D5S050MGK",
|
||||
Content: "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
|
||||
Content: "<p>this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it</p>",
|
||||
Text: "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWDY9PDNHDBDBBFTJKJY8XE",
|
||||
ContentWarning: "test: you shouldn't be able to interact with this post in any way",
|
||||
Visibility: gtsmodel.VisibilityMutualsOnly,
|
||||
|
@ -1616,7 +1590,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01F8MH82FYRXD2RC6108DAJ5HB",
|
||||
URI: "http://localhost:8080/users/the_mighty_zork/statuses/01F8MH82FYRXD2RC6108DAJ5HB",
|
||||
URL: "http://localhost:8080/@the_mighty_zork/statuses/01F8MH82FYRXD2RC6108DAJ5HB",
|
||||
Content: "here's a little gif of trent.... and also a cow",
|
||||
Content: "<p>here's a little gif of trent.... and also a cow</p>",
|
||||
Text: "here's a little gif of trent.... and also a cow",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
AttachmentIDs: []string{"01F8MH7TDVANYKWVE8VVKFPJTJ", "01CDR64G398ADCHXK08WWTHEZ5"},
|
||||
|
@ -1625,8 +1599,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWE0H2GKH794Q7GDPANH91Q",
|
||||
ContentWarning: "eye contact, trent reznor gif, cow",
|
||||
Visibility: gtsmodel.VisibilityMutualsOnly,
|
||||
|
@ -1641,19 +1613,15 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01FCTA44PW9H1TB328S9AQXKDS",
|
||||
URI: "http://localhost:8080/users/the_mighty_zork/statuses/01FCTA44PW9H1TB328S9AQXKDS",
|
||||
URL: "http://localhost:8080/@the_mighty_zork/statuses/01FCTA44PW9H1TB328S9AQXKDS",
|
||||
Content: "hi!",
|
||||
Content: "<p>hi!</p>",
|
||||
Text: "hi!",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
AttachmentIDs: []string{},
|
||||
CreatedAt: TimeMustParse("2022-05-20T11:37:55Z"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWE1ERQSMMVWDD0BE491E2P",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
@ -1666,19 +1634,13 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01HEN2RZ8BG29Y5Z9VJC73HZW7",
|
||||
URI: "http://localhost:8080/users/the_mighty_zork/statuses/065TKBPE0H2AH8S5X8JCK4XC58",
|
||||
URL: "http://localhost:8080/@the_mighty_zork/statuses/065TKBPE0H2AH8S5X8JCK4XC58",
|
||||
Content: "what do you think of sloths?",
|
||||
Content: "<p>what do you think of sloths?</p>",
|
||||
Text: "what do you think of sloths?",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
AttachmentIDs: nil,
|
||||
CreatedAt: TimeMustParse("2022-05-20T11:41:10Z"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
@ -1696,12 +1658,9 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Text: "Here's a bunch of HTML, read it and weep, weep then!\n\n```html\n<section class=\"about-user\">\n <div class=\"col-header\">\n <h2>About</h2>\n </div> \n <div class=\"fields\">\n <h3 class=\"sr-only\">Fields</h3>\n <dl>\n <div class=\"field\">\n <dt>should you follow me?</dt>\n <dd>maybe!</dd>\n </div>\n <div class=\"field\">\n <dt>age</dt>\n <dd>120</dd>\n </div>… <h3 class=\"sr-only\">Stats</h3>\n <span>Joined in Jun, 2022.</span>\n <span>8 posts.</span>\n <span>Followed by 1.</span>\n <span>Following 1.</span>\n </div>\n <div class=\"accountstats\" aria-hidden=\"true\">\n <b>Joined</b><time datetime=\"2022-06-04T13:12:00.000Z\">Jun, 2022</time>\n <b>Posts</b><span>8</span>\n <b>Followed by</b><span>1</span>\n <b>Following</b><span>1</span>\n </div>\n</section>\n```\n\nThere, hope you liked that!",
|
||||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
CreatedAt: TimeMustParse("2023-12-10T11:24:00+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HH9M3FVSF5J7120X9T6PG4GF",
|
||||
ContentWarning: "HTML in post",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
|
@ -1721,16 +1680,13 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
AttachmentIDs: []string{"01J2M20K6K9XQC4WSB961YJHV6"},
|
||||
CreatedAt: TimeMustParse("2024-01-10T11:24:00+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToID: "01FF25D5Q0DH7CHD57CTRS6WK0",
|
||||
InReplyToAccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToURI: "http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
@ -1746,17 +1702,11 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Text: "this is the latest revision of the status, with a content-warning",
|
||||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
ContentWarning: "edited status",
|
||||
AttachmentIDs: nil,
|
||||
CreatedAt: TimeMustParse("2024-11-01T11:00:00+02:00"),
|
||||
EditedAt: TimeMustParse("2024-11-01T11:02:00+02:00"),
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToID: "",
|
||||
InReplyToAccountID: "",
|
||||
InReplyToURI: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "",
|
||||
EditIDs: []string{"01JDPZCZ2Y9KSGZW0R7ZG8T8Y2", "01JDPZDADMD1T9HKF94RECF7PP"},
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
|
@ -1769,16 +1719,13 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01F8MHBQCBTDKN6X5VHGMMN4MA",
|
||||
URI: "http://localhost:8080/users/1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA",
|
||||
URL: "http://localhost:8080/@1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA",
|
||||
Content: "🐢 hi everyone i post about turtles 🐢",
|
||||
Content: "<p>🐢 hi everyone i post about turtles 🐢</p>",
|
||||
Text: "🐢 hi everyone i post about turtles 🐢",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/1happyturtle",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWE2Q24FWCZE41AS77SDFRZ",
|
||||
ContentWarning: "introduction post",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
|
@ -1793,18 +1740,14 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01F8MHC0H0A7XHTVH5F596ZKBM",
|
||||
URI: "http://localhost:8080/users/1happyturtle/statuses/01F8MHC0H0A7XHTVH5F596ZKBM",
|
||||
URL: "http://localhost:8080/@1happyturtle/statuses/01F8MHC0H0A7XHTVH5F596ZKBM",
|
||||
Content: "🐢 this one is federated, likeable, and boostable but not replyable 🐢",
|
||||
Content: "<p>🐢 this one is federated, likeable, and boostable but not replyable 🐢</p>",
|
||||
Text: "🐢 this one is federated, likeable, and boostable but not replyable 🐢",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/1happyturtle",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWE3P291Z3NJEJVFPW0K9ZQ",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(true),
|
||||
Language: "en",
|
||||
|
@ -1828,16 +1771,13 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01F8MHC8VWDRBQR0N1BATDDEM5",
|
||||
URI: "http://localhost:8080/users/1happyturtle/statuses/01F8MHC8VWDRBQR0N1BATDDEM5",
|
||||
URL: "http://localhost:8080/@1happyturtle/statuses/01F8MHC8VWDRBQR0N1BATDDEM5",
|
||||
Content: "🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢",
|
||||
Content: "<p>🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢</p>",
|
||||
Text: "🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/1happyturtle",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWE4P0EW9HBA5WHW97D5YV0",
|
||||
ContentWarning: "you won't be able to reply to this without my approval",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
|
@ -1864,18 +1804,14 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01F8MHCP5P2NWYQ416SBA0XSEV",
|
||||
URI: "http://localhost:8080/users/1happyturtle/statuses/01F8MHCP5P2NWYQ416SBA0XSEV",
|
||||
URL: "http://localhost:8080/@1happyturtle/statuses/01F8MHCP5P2NWYQ416SBA0XSEV",
|
||||
Content: "🐢 this is a public status but I want it local only and not boostable 🐢",
|
||||
Content: "<p>🐢 this is a public status but I want it local only and not boostable 🐢</p>",
|
||||
Text: "🐢 this is a public status but I want it local only and not boostable 🐢",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/1happyturtle",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWE5JXFPFP3P5W2QNHVVV27",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(true),
|
||||
Language: "en",
|
||||
|
@ -1899,11 +1835,10 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01FCQSQ667XHJ9AV9T27SJJSX5",
|
||||
URI: "http://localhost:8080/users/1happyturtle/statuses/01FCQSQ667XHJ9AV9T27SJJSX5",
|
||||
URL: "http://localhost:8080/@1happyturtle/statuses/01FCQSQ667XHJ9AV9T27SJJSX5",
|
||||
Content: "🐢 @the_mighty_zork hi zork! 🐢",
|
||||
Content: "<p>🐢 <span class=\"h-card\"><a href=\"http://localhost:8080/@1happyturtle\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">@<span>the_mighty_zork</span></a></span> hi zork! 🐢</p>",
|
||||
Text: "🐢 @the_mighty_zork hi zork! 🐢",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/1happyturtle",
|
||||
MentionIDs: []string{"01FDF2HM2NF6FSRZCDEDV451CN"},
|
||||
|
@ -1911,9 +1846,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
InReplyToID: "01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
InReplyToAccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
InReplyToURI: "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
@ -1926,21 +1859,15 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01FN3VJGFH10KR7S2PB0GFJZYG",
|
||||
URI: "http://localhost:8080/users/1happyturtle/statuses/01FN3VJGFH10KR7S2PB0GFJZYG",
|
||||
URL: "http://localhost:8080/@1happyturtle/statuses/01FN3VJGFH10KR7S2PB0GFJZYG",
|
||||
Content: "🐢 @the_mighty_zork hi zork, this is a direct message, shhhhhh! 🐢",
|
||||
Content: "<p>🐢 <span class=\"h-card\"><a href=\"http://localhost:8080/@1happyturtle\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">@<span>the_mighty_zork</span></a></span> hi zork, this is a direct message, shhhhhh! 🐢</p>",
|
||||
Text: "🐢 @the_mighty_zork hi zork, this is a direct message, shhhhhh! 🐢",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/1happyturtle",
|
||||
MentionIDs: []string{"01FDF2HM2NF6FSRZCDEDV451CN"},
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
InReplyToID: "",
|
||||
InReplyToAccountID: "",
|
||||
InReplyToURI: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWE71MGRRDSHBKXFD5DDSWR",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityDirect,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
@ -1954,19 +1881,15 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
PinnedAt: TimeMustParse("2021-03-18T09:13:55+02:00"),
|
||||
URI: "http://localhost:8080/users/1happyturtle/statuses/01G20ZM733MGN8J344T4ZDDFY1",
|
||||
URL: "http://localhost:8080/@1happyturtle/statuses/01G20ZM733MGN8J344T4ZDDFY1",
|
||||
Content: "🐢 hi followers! did u know i'm a turtle? 🐢",
|
||||
Content: "<p>🐢 hi followers! did u know i'm a turtle? 🐢</p>",
|
||||
Text: "🐢 hi followers! did u know i'm a turtle? 🐢",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
AttachmentIDs: []string{},
|
||||
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/1happyturtle",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "01HCWE7ZNC2SS4P05WA5QYED23",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
@ -1979,19 +1902,14 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01HEN2PRXT0TF4YDRA64FZZRN7",
|
||||
URI: "http://localhost:8080/users/1happyturtle/statuses/01HEN2PRXT0TF4YDRA64FZZRN7",
|
||||
URL: "http://localhost:8080/@1happyturtle/statuses/01HEN2PRXT0TF4YDRA64FZZRN7",
|
||||
Content: "hey everyone i got stuck in a shed. any ideas for how to get out?",
|
||||
Content: "<p>hey everyone i got stuck in a shed. any ideas for how to get out?</p>",
|
||||
Text: "hey everyone i got stuck in a shed. any ideas for how to get out?",
|
||||
ContentType: gtsmodel.StatusContentTypePlain,
|
||||
AttachmentIDs: nil,
|
||||
CreatedAt: TimeMustParse("2021-07-28T10:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/1happyturtle",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
@ -2015,11 +1933,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
InReplyToID: "",
|
||||
InReplyToAccountID: "",
|
||||
InReplyToURI: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "",
|
||||
EditIDs: []string{"01JDPZPBXAX0M02YSEPB21KX4R", "01JDPZPJHKP7E3M0YQXEXPS1YT", "01JDPZPY3F85Y7B78ETRXEMWD9"},
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
|
@ -2032,23 +1945,15 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01FVW7JHQFSFK166WWKR8CBA6M",
|
||||
URI: "http://fossbros-anonymous.io/users/foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M",
|
||||
URL: "http://fossbros-anonymous.io/@foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M",
|
||||
Content: "dark souls status bot: \"thoughts of dog\"",
|
||||
Content: "<p>dark souls status bot: \"thoughts of dog\"</p>",
|
||||
AttachmentIDs: []string{"01FVW7RXPQ8YJHTEXYPE7Q8ZY0"},
|
||||
CreatedAt: TimeMustParse("2021-09-20T12:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
MentionIDs: []string{},
|
||||
AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
InReplyToID: "",
|
||||
InReplyToAccountID: "",
|
||||
InReplyToURI: "",
|
||||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
PendingApproval: util.Ptr(false),
|
||||
|
@ -2057,22 +1962,15 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01HEN2QRFA8H3C6QPN7RD4KSR6",
|
||||
URI: "http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6",
|
||||
URL: "http://fossbros-anonymous.io/@foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6",
|
||||
Content: "what products should i buy at the grocery store?",
|
||||
Content: "<p>what products should i buy at the grocery store?</p>",
|
||||
AttachmentIDs: []string{"01FVW7RXPQ8YJHTEXYPE7Q8ZY0"},
|
||||
CreatedAt: TimeMustParse("2021-09-11T11:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
InReplyToID: "",
|
||||
InReplyToAccountID: "",
|
||||
InReplyToURI: "",
|
||||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ActivityQuestion,
|
||||
PollID: "01HEN2R65468ZG657C4ZPHJ4EX",
|
||||
|
@ -2082,22 +1980,15 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
ID: "01HEWV37MHV8BAC8ANFGVRRM5D",
|
||||
URI: "http://fossbros-anonymous.io/users/foss_satan/statuses/01HEWV37MHV8BAC8ANFGVRRM5D",
|
||||
URL: "http://fossbros-anonymous.io/@foss_satan/statuses/01HEWV37MHV8BAC8ANFGVRRM5D",
|
||||
Content: "what products should i buy at the grocery store? (now an endless poll!)",
|
||||
Content: "<p>what products should i buy at the grocery store? (now an endless poll!)</p>",
|
||||
AttachmentIDs: []string{"01FVW7RXPQ8YJHTEXYPE7Q8ZY0"},
|
||||
CreatedAt: TimeMustParse("2021-09-11T11:40:37+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
InReplyToID: "",
|
||||
InReplyToAccountID: "",
|
||||
InReplyToURI: "",
|
||||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ActivityQuestion,
|
||||
PollID: "01HEWV1GW2D49R919NPEDXPTZ5",
|
||||
|
@ -2110,18 +2001,11 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Content: "<p>this is the latest status edit without poll change</p>",
|
||||
Text: "this is the latest status edit without poll change",
|
||||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
ContentWarning: "",
|
||||
AttachmentIDs: nil,
|
||||
CreatedAt: TimeMustParse("2024-11-01T09:00:00+02:00"),
|
||||
EditedAt: TimeMustParse("2024-11-01T09:02:00+02:00"),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
InReplyToID: "",
|
||||
InReplyToAccountID: "",
|
||||
InReplyToURI: "",
|
||||
BoostOfID: "",
|
||||
ThreadID: "",
|
||||
EditIDs: []string{"01JDQ07ZZ4FGP13YN8TF63P5A6", "01JDQ08AYQC0G6413VAHA51CV9"},
|
||||
PollID: "01JDQ0EZ5HM9T4WXRQ5WSVD40J",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
|
@ -2138,7 +2022,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Content: `<p>hi <span class="h-card"><a href="http://localhost:8080/@admin" class="u-url mention" rel="nofollow noreferrer noopener" target="_blank">@<span>admin</span></a></span> here's some media for ya</p>`,
|
||||
AttachmentIDs: []string{"01HE7Y3C432WRSNS10EZM86SA5", "01HE7ZFX9GKA5ZZVD4FACABSS9", "01HE88YG74PVAB81PX2XA9F3FG"},
|
||||
CreatedAt: TimeMustParse("2023-11-02T12:44:25+02:00"),
|
||||
EditedAt: time.Time{},
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://example.org/users/Some_User",
|
||||
MentionIDs: []string{"01HE7XQNMKTVC8MNPCE1JGK4J3"},
|
||||
|
@ -2146,12 +2029,10 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
InReplyToID: "01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
InReplyToAccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToURI: "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
BoostOfID: "",
|
||||
ContentWarning: "some unknown media included",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(true),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
PendingApproval: util.Ptr(false),
|
||||
|
@ -3638,14 +3519,10 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
|
|||
"local_account_1_status_9_edit_1": {
|
||||
ID: "01JDPZCZ2Y9KSGZW0R7ZG8T8Y2",
|
||||
Content: "<p>this is the original status</p>",
|
||||
ContentWarning: "",
|
||||
Text: "this is the original status",
|
||||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
Language: "en",
|
||||
Sensitive: util.Ptr(false),
|
||||
AttachmentIDs: nil,
|
||||
PollOptions: nil,
|
||||
PollVotes: nil,
|
||||
StatusID: "01JDPZC707CKDN8N4QVWM4Z1NR",
|
||||
CreatedAt: TimeMustParse("2024-11-01T11:00:00+02:00"),
|
||||
},
|
||||
|
@ -3657,23 +3534,16 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
|
|||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
Language: "en",
|
||||
Sensitive: util.Ptr(false),
|
||||
AttachmentIDs: nil,
|
||||
PollOptions: nil,
|
||||
PollVotes: nil,
|
||||
StatusID: "01JDPZC707CKDN8N4QVWM4Z1NR",
|
||||
CreatedAt: TimeMustParse("2024-11-01T11:01:00+02:00"),
|
||||
},
|
||||
"local_account_2_status_9_edit_1": {
|
||||
ID: "01JDPZPBXAX0M02YSEPB21KX4R",
|
||||
Content: "<p>this is the original status</p>",
|
||||
ContentWarning: "",
|
||||
Text: "this is the original status",
|
||||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
Language: "en",
|
||||
Sensitive: util.Ptr(false),
|
||||
AttachmentIDs: nil,
|
||||
PollOptions: nil,
|
||||
PollVotes: nil,
|
||||
StatusID: "01JDPZEZ77X1NX0TY9M10BK1HM",
|
||||
CreatedAt: TimeMustParse("2024-11-01T10:00:00+02:00"),
|
||||
},
|
||||
|
@ -3686,8 +3556,6 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
|
|||
Language: "en",
|
||||
Sensitive: util.Ptr(true),
|
||||
AttachmentIDs: []string{"01JDQ164HM08SGJ7ZEK9003Z4B"},
|
||||
PollOptions: nil,
|
||||
PollVotes: nil,
|
||||
StatusID: "01JDPZEZ77X1NX0TY9M10BK1HM",
|
||||
CreatedAt: TimeMustParse("2024-11-01T10:01:00+02:00"),
|
||||
},
|
||||
|
@ -3699,21 +3567,16 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
|
|||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
Language: "en",
|
||||
Sensitive: util.Ptr(false),
|
||||
AttachmentIDs: nil,
|
||||
PollOptions: nil,
|
||||
PollVotes: nil,
|
||||
StatusID: "01JDPZEZ77X1NX0TY9M10BK1HM",
|
||||
CreatedAt: TimeMustParse("2024-11-01T10:02:00+02:00"),
|
||||
},
|
||||
"remote_account_1_status_4_edit_1": {
|
||||
ID: "01JDQ07ZZ4FGP13YN8TF63P5A6",
|
||||
Content: "<p>this is the original status, with a poll!</p>",
|
||||
ContentWarning: "",
|
||||
Text: "this is the original status, with a poll!",
|
||||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
Language: "en",
|
||||
Sensitive: util.Ptr(false),
|
||||
AttachmentIDs: nil,
|
||||
PollOptions: []string{"yes", "no", "spiderman"},
|
||||
PollVotes: []int{42, 42, 69},
|
||||
StatusID: "01JDQ07JZTX9CMDJP67CNA71YD",
|
||||
|
@ -3727,7 +3590,6 @@ func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit {
|
|||
ContentType: gtsmodel.StatusContentTypeMarkdown,
|
||||
Language: "en",
|
||||
Sensitive: util.Ptr(false),
|
||||
AttachmentIDs: nil,
|
||||
PollOptions: []string{"yes", "no", "maybe", "i don't know", "can you repeat the question"},
|
||||
PollVotes: []int{0, 0, 0, 0, 1},
|
||||
StatusID: "01JDQ07JZTX9CMDJP67CNA71YD",
|
||||
|
|
|
@ -141,15 +141,10 @@ main {
|
|||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 0;
|
||||
grid-row: span 1;
|
||||
grid-column: 1 / span 3;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
.text > .content,
|
||||
.text-spoiler > summary > .spoiler-content {
|
||||
word-break: break-word;
|
||||
line-height: 1.6rem;
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
|
@ -157,11 +152,6 @@ main {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content {
|
||||
word-break: break-word;
|
||||
line-height: 1.6rem;
|
||||
width: 100%;
|
||||
|
||||
/*
|
||||
Normalize header sizes to fit better
|
||||
with the line-height we use for statuses.
|
||||
|
@ -197,6 +187,17 @@ main {
|
|||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 0;
|
||||
grid-row: span 1;
|
||||
grid-column: 1 / span 3;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
width: 100%;
|
||||
|
||||
.poll {
|
||||
background-color: $gray2;
|
||||
z-index: 2;
|
||||
|
|
|
@ -126,15 +126,15 @@ function StatusBody({ status }: { status: StatusType }) {
|
|||
<div className="status-body">
|
||||
<details className="text-spoiler">
|
||||
<summary>
|
||||
<span
|
||||
className="spoiler-text"
|
||||
<div
|
||||
className="spoiler-content"
|
||||
lang={status.language}
|
||||
>
|
||||
{ status.spoiler_text
|
||||
? status.spoiler_text + " "
|
||||
: "[no content warning set] "
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
className="button"
|
||||
role="button"
|
||||
|
|
|
@ -38,7 +38,9 @@
|
|||
{{- if .SpoilerText }}
|
||||
<details class="text-spoiler">
|
||||
<summary>
|
||||
<span class="spoiler-text p-summary" lang="{{- .LanguageTag.TagStr -}}">{{- emojify .Emojis (escape .SpoilerText) -}}</span>
|
||||
<div class="spoiler-content p-summary" lang="{{- .LanguageTag.TagStr -}}">
|
||||
{{ noescape .SpoilerContent | emojify .Emojis }}
|
||||
</div>
|
||||
<span class="button" role="button" tabindex="0">Toggle visibility</span>
|
||||
</summary>
|
||||
<div class="text">
|
||||
|
|
Loading…
Reference in New Issue