[feature] support for toot:indexable (#4538)

Adds account DB column, to/from AP and API formats, checkbox in settings UI. Doesn't change our own search behavior.

- Fixes #3062

## Remaining work

- [x] Add `noindex` to account serialization for client API for back compat
- [x] ~~Update templates to use `indexable` instead of `discoverable` to control `noindex` meta tags~~
- [x] Given that our profile public web page page also serves posts, I've opted to require both `indexable` and `discoverable` be true to remove `noindex` tags
- [x] Update docs to reflect that change
- [x] DB migration should duplicate `discoverable` to `indexable` rather than defaulting to `false`
- [x] Look at addressing #3064 by having a thread's collective `indexable` control the `noindex` meta tag for `thread.tmpl`

Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4538
Reviewed-by: tobi <kipvandenbos@noreply.codeberg.org>
Co-authored-by: Vyr Cossont <vyr@noreply.codeberg.org>
Co-committed-by: Vyr Cossont <vyr@noreply.codeberg.org>
This commit is contained in:
Vyr Cossont
2025-11-14 12:43:30 +01:00
committed by kim
co-authored by tobi
parent 409380304d
commit d489dcb171
47 changed files with 947 additions and 17 deletions
+24
View File
@@ -433,6 +433,10 @@ definitions:
example: 01FBVD42CQ3ZEEVMW180SBX03B
type: string
x-go-name: ID
indexable:
description: Account has opted its posts into full-text search features.
type: boolean
x-go-name: Indexable
last_status_at:
description: When the account's most recent status was posted (ISO 8601 Date).
example: "2021-07-30"
@@ -444,6 +448,12 @@ definitions:
x-go-name: Locked
moved:
$ref: '#/definitions/account'
noindex:
description: |-
Account has *not* opted its posts into full-text search features.
Opposite sense of Indexable for compatibility with `masto-fe-standalone`.
type: boolean
x-go-name: NoIndex
note:
description: Bio/description of this account.
type: string
@@ -2759,6 +2769,10 @@ definitions:
example: 01FBVD42CQ3ZEEVMW180SBX03B
type: string
x-go-name: ID
indexable:
description: Account has opted its posts into full-text search features.
type: boolean
x-go-name: Indexable
last_status_at:
description: When the account's most recent status was posted (ISO 8601 Date).
example: "2021-07-30"
@@ -2777,6 +2791,12 @@ definitions:
example: "2021-07-30T09:20:25+00:00"
type: string
x-go-name: MuteExpiresAt
noindex:
description: |-
Account has *not* opted its posts into full-text search features.
Opposite sense of Indexable for compatibility with `masto-fe-standalone`.
type: boolean
x-go-name: NoIndex
note:
description: Bio/description of this account.
type: string
@@ -5350,6 +5370,10 @@ paths:
in: formData
name: discoverable
type: boolean
- description: Account's posts should be made indexable by full-text search features (if enabled).
in: formData
name: indexable
type: boolean
- description: Account is flagged as a bot.
in: formData
name: bot
+30 -7
View File
@@ -149,20 +149,43 @@ After ticking or unticking the checkbox, be sure to click on the `Save profile i
#### Mark Account as Discoverable by Search Engines and Directories
This setting updates the 'discoverable' flag on your account.
This setting updates the 'discoverable' setting on your account.
Checking the discoverable box for your account does the following:
- Update robots meta tags for your account, allowing it to be indexed by search engines and appear in search engine results.
- Indicate to remote instances that your account may be included in public directories and indexes.
- If 'indexable' is also checked, update robots meta tags for your account, allowing your profile and posts to be indexed by web search engines and appear in web search engine results.
Turning on the discoverable flag may take a week or more to propagate; your account will not immediately appear in search engine results.
!!! tip
Discoverable is set to false by default for new accounts, to avoid exposing them to crawlers. Setting it to true is useful for public-facing accounts where you actually *want* to be crawled.
Turning on the discoverable setting may take a week or more to propagate; your account will not immediately appear in search results.
!!! info
The discoverable setting is about **discoverability of your account**, not searchability of your posts. It has nothing to do with indexing of your posts for search by Mastodon instances, or other federated instances that use full text search!
The discoverable setting is about **discoverability of your account**, not searchability of your posts. The indexable setting controls the rest.
#### Mark Account's Posts as Full-Text Indexable
This setting updates the 'indexable' setting on your account.
Checking the indexable box for your account does the following:
- Indicate that your account's posts may be included in full-text search indexes. This includes but is not limited to [Mastodon instances with the optional full-text search capability](https://docs.joinmastodon.org/admin/elasticsearch/); other Fediverse instance types and non-instance services may also check this flag.
- If 'discoverable' is also checked, update robots meta tags for your account, allowing your profile and posts to be indexed by web search engines and appear in web search engine results.
As GoToSocial doesn't currently have general full-text search capability, the indexable setting doesn't currently affect the behavior of your own or other GoToSocial instances, but this may change in the future.
Turning on the indexable setting may take a week or more to propagate; your posts will not immediately appear in search results.
!!! info
The indexable setting is specifically about **searchability of your posts**, but if you turn it on, you will generally also want to turn on discoverability as well.
#### Visibility to Web Crawlers
GoToSocial serves your profile and public posts on the same public web page. If you enable both the 'discoverable' and 'indexable' settings, robots meta tags on that page will be updated to allow well-behaved web crawlers (such as search engines) to index it.
!!! warning
Robots meta tags are a convention, not a security mechanism: many web crawlers are *not* well-behaved and do not respect robots meta tags. Don't put anything in your profile that you don't want the entire internet to see. The same applies to public posts.
!!! tip
'discoverable' and 'indexable' are set to false by default for new accounts, to avoid exposing them to crawlers. Setting them to true is useful for public-facing accounts where you actually *want* to be crawled.
#### Enable RSS Feed of Public Posts
+7
View File
@@ -247,6 +247,7 @@ type Accountable interface {
WithSummary
WithAttachment
WithDiscoverable
WithIndexable
WithURL
WithPublicKey
WithInbox
@@ -550,6 +551,12 @@ type WithDiscoverable interface {
SetTootDiscoverable(vocab.TootDiscoverableProperty)
}
// WithIndexable represents an activity with TootIndexableProperty
type WithIndexable interface {
GetTootIndexable() vocab.TootIndexableProperty
SetTootIndexable(vocab.TootIndexableProperty)
}
// WithURL represents an activity with ActivityStreamsUrlProperty
type WithURL interface {
GetActivityStreamsUrl() vocab.ActivityStreamsUrlProperty
+21
View File
@@ -607,6 +607,27 @@ func SetDiscoverable(with WithDiscoverable, discoverable bool) {
discoverProp.Set(discoverable)
}
// GetIndexable returns the boolean contained in the Indexable property of 'with'.
//
// Returns default 'false' if property unusable or not set.
func GetIndexable(with WithIndexable) bool {
indexableProp := with.GetTootIndexable()
if indexableProp == nil || !indexableProp.IsXMLSchemaBoolean() {
return false
}
return indexableProp.Get()
}
// SetIndexable sets the given boolean on the Indexable property of 'with'.
func SetIndexable(with WithIndexable, indexable bool) {
indexableProp := with.GetTootIndexable()
if indexableProp == nil {
indexableProp = streams.NewTootIndexableProperty()
with.SetTootIndexable(indexableProp)
}
indexableProp.Set(indexable)
}
// GetManuallyApprovesFollowers returns the boolean contained in the ManuallyApprovesFollowers property of 'with'.
//
// Returns default 'false' if property unusable or not set.
@@ -380,6 +380,7 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
suite.EqualValues(requestingAccount.MovedToURI, dbUpdatedAccount.MovedToURI)
suite.EqualValues(requestingAccount.Locked, dbUpdatedAccount.Locked)
suite.EqualValues(requestingAccount.Discoverable, dbUpdatedAccount.Discoverable)
suite.EqualValues(requestingAccount.Indexable, dbUpdatedAccount.Indexable)
suite.EqualValues(requestingAccount.URI, dbUpdatedAccount.URI)
suite.EqualValues(requestingAccount.URL, dbUpdatedAccount.URL)
suite.EqualValues(requestingAccount.InboxURI, dbUpdatedAccount.InboxURI)
@@ -444,6 +445,7 @@ func (suite *InboxPostTestSuite) TestPostDelete() {
suite.Empty(dbAccount.HeaderRemoteURL)
suite.Empty(dbAccount.Fields)
suite.False(*dbAccount.Discoverable)
suite.False(*dbAccount.Indexable)
suite.WithinDuration(time.Now(), dbAccount.SuspendedAt, 30*time.Second)
suite.Equal(dbAccount.ID, dbAccount.SuspensionOrigin)
}
@@ -55,6 +55,11 @@ import (
// description: Account should be made discoverable and shown in the profile directory (if enabled).
// type: boolean
// -
// name: indexable
// in: formData
// description: Account's posts should be made indexable by full-text search features (if enabled).
// type: boolean
// -
// name: bot
// in: formData
// description: Account is flagged as a bot.
@@ -352,6 +357,7 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest,
if form == nil ||
(form.Discoverable == nil &&
form.Indexable == nil &&
form.Bot == nil &&
form.DisplayName == nil &&
form.Note == nil &&
@@ -359,6 +359,61 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableJSON() {
suite.False(*dbZork.Discoverable)
}
func (suite *AccountUpdateTestSuite) TestUpdateAccountIndexableForm() {
data := map[string][]string{
"indexable": {"false"},
}
apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "")
if err != nil {
suite.FailNow(err.Error())
}
suite.False(apimodelAccount.Indexable)
// Check the account in the database too.
dbZork, err := suite.db.GetAccountByID(suite.T().Context(), apimodelAccount.ID)
suite.NoError(err)
suite.False(*dbZork.Indexable)
}
func (suite *AccountUpdateTestSuite) TestUpdateAccountIndexableFormData() {
data := map[string][]string{
"indexable": {"false"},
}
apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "")
if err != nil {
suite.FailNow(err.Error())
}
suite.False(apimodelAccount.Indexable)
// Check the account in the database too.
dbZork, err := suite.db.GetAccountByID(suite.T().Context(), apimodelAccount.ID)
suite.NoError(err)
suite.False(*dbZork.Indexable)
}
func (suite *AccountUpdateTestSuite) TestUpdateAccountIndexableJSON() {
data := `
{
"indexable": false
}`
apimodelAccount, err := suite.updateAccountFromJSON(data, http.StatusOK, "")
if err != nil {
suite.FailNow(err.Error())
}
suite.False(apimodelAccount.Indexable)
// Check the account in the database too.
dbZork, err := suite.db.GetAccountByID(suite.T().Context(), apimodelAccount.ID)
suite.NoError(err)
suite.False(*dbZork.Indexable)
}
func (suite *AccountUpdateTestSuite) TestUpdateAccountWithImageFormData() {
data := map[string][]string{
"display_name": {"updated zork display name!!!"},
@@ -88,6 +88,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "<p>i post about things that concern me</p>",
@@ -148,6 +150,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -204,6 +208,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": true,
"created_at": "2020-05-17T13:10:59.000Z",
"note": "",
@@ -251,6 +257,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "",
"locked": false,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2025-03-15T11:08:00.000Z",
"note": "<p>I'm a test account that posts a shitload of media and I have my account rendered in \"gallery\" mode</p>",
@@ -314,6 +322,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "<p>hey yo this is my profile!</p>",
@@ -366,6 +376,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "",
"locked": false,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "",
@@ -414,6 +426,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "some user",
"locked": true,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2020-08-10T12:13:28.000Z",
"note": "i'm a real son of a gun",
@@ -461,6 +475,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -508,6 +524,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "lizzzieeeeeeeeeeee",
"locked": true,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2020-08-10T12:13:28.000Z",
"note": "if i die blame charles don't let that fuck become king",
@@ -556,6 +574,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "",
"locked": false,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2020-08-10T12:13:28.000Z",
"note": "",
@@ -642,6 +662,8 @@ func (suite *AccountsGetTestSuite) TestAccountsMinID() {
"display_name": "",
"locked": false,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2025-03-15T11:08:00.000Z",
"note": "<p>I'm a test account that posts a shitload of media and I have my account rendered in \"gallery\" mode</p>",
@@ -175,6 +175,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -222,6 +224,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -282,6 +286,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -338,6 +344,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -407,6 +415,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -467,6 +477,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -517,6 +529,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -679,6 +693,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetCreatedByAccount() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -739,6 +755,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetCreatedByAccount() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -789,6 +807,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetCreatedByAccount() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -951,6 +971,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetTargetAccount() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -1011,6 +1033,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetTargetAccount() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -1061,6 +1085,8 @@ func (suite *ReportsGetTestSuite) TestReportsGetTargetAccount() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -88,6 +88,8 @@ func (suite *GetTestSuite) TestGet() {
"display_name": "some user",
"locked": true,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2020-08-10T12:13:28.000Z",
"note": "i'm a real son of a gun",
@@ -84,6 +84,8 @@ func (suite *GetOutgoingTestSuite) TestGetOutgoing() {
"display_name": "some user",
"locked": true,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2020-08-10T12:13:28.000Z",
"note": "i'm a real son of a gun",
@@ -169,6 +169,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -313,6 +315,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -457,6 +461,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -652,6 +658,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -822,6 +830,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -1003,6 +1013,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
+38 -2
View File
@@ -18,6 +18,7 @@
package mutes_test
import (
"bytes"
"encoding/json"
"io"
"net/http"
@@ -145,9 +146,44 @@ func (suite *MutesTestSuite) TestIndefinitelyMutedAccountSerializesMuteExpiratio
suite.FailNow(err.Error())
}
// Fetch all muted accounts for the logged-in account.
// The expected body contains `"mute_expires_at":null`.
_, err = suite.getMutedAccounts(http.StatusOK, `[{"id":"01F8MH5ZK5VRH73AKHQM6Y9VNX","username":"foss_satan","acct":"foss_satan@fossbros-anonymous.io","display_name":"big gerald","locked":false,"discoverable":true,"bot":false,"created_at":"2021-09-26T10:52:36.000Z","note":"i post about like, i dunno, stuff, or whatever!!!!","url":"http://fossbros-anonymous.io/@foss_satan","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":0,"following_count":0,"statuses_count":4,"last_status_at":"2024-11-01","emojis":[],"fields":[],"group":false,"mute_expires_at":null}]`)
expectedBodyIndented := `[
{
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
"username": "foss_satan",
"acct": "foss_satan@fossbros-anonymous.io",
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
"url": "http://fossbros-anonymous.io/@foss_satan",
"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": 0,
"following_count": 0,
"statuses_count": 4,
"last_status_at": "2024-11-01",
"emojis": [],
"fields": [],
"group": false,
"mute_expires_at": null
}
]
`
var expectedBody bytes.Buffer
if err = json.Compact(&expectedBody, []byte(expectedBodyIndented)); err != nil {
suite.FailNow(err.Error())
}
// Fetch all muted accounts for the logged-in account.
_, err = suite.getMutedAccounts(http.StatusOK, expectedBody.String())
if err != nil {
suite.FailNow(err.Error())
}
@@ -119,6 +119,8 @@ func (suite *ReportGetTestSuite) TestGetReport1() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -145,6 +145,8 @@ func (suite *ReportsGetTestSuite) TestGetReports() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -237,6 +239,8 @@ func (suite *ReportsGetTestSuite) TestGetReports4() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -313,6 +317,8 @@ func (suite *ReportsGetTestSuite) TestGetReports6() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -373,6 +379,8 @@ func (suite *ReportsGetTestSuite) TestGetReports7() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -102,6 +102,8 @@ func (suite *StatusHistoryTestSuite) TestGetHistory() {
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -121,6 +121,8 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -226,6 +228,8 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
+7
View File
@@ -48,6 +48,11 @@ type Account struct {
Locked bool `json:"locked"`
// Account has opted into discovery features.
Discoverable bool `json:"discoverable"`
// Account has opted its posts into full-text search features.
Indexable bool `json:"indexable"`
// Account has *not* opted its posts into full-text search features.
// Opposite sense of Indexable for compatibility with `masto-fe-standalone`.
NoIndex bool `json:"noindex"`
// Account identifies as a bot.
Bot bool `json:"bot"`
// When the account was created (ISO 8601 Datetime).
@@ -208,6 +213,8 @@ type AccountCreateRequest struct {
type UpdateCredentialsRequest struct {
// Account should be made discoverable and shown in the profile directory (if enabled).
Discoverable *bool `form:"discoverable" json:"discoverable"`
// Account should be made indexable and opted into full-text search (if enabled).
Indexable *bool `form:"indexable" json:"indexable"`
// Account is flagged as a bot.
Bot *bool `form:"bot" json:"bot"`
// The display name to use for the account.
+4
View File
@@ -40,6 +40,10 @@ type WebThreadContext struct {
// status, and descendants.
Statuses []*WebStatus
// True only if all visible Statuses
// are from indexable accounts.
Indexable bool
// Total length of
// the main thread.
ThreadLength int
+1 -1
View File
@@ -24,7 +24,7 @@ package util
// - https://www.rfc-editor.org/rfc/rfc9309.html
const (
RobotsDirectivesDisallow = "noindex, nofollow"
RobotsDirectivesAllowSome = "nofollow, noarchive, nositelinkssearchbox, max-image-preview:standard"
RobotsDirectivesAllowSome = "noarchive, nositelinkssearchbox, max-image-preview:standard"
RobotsTxt = `# GoToSocial robots.txt -- to edit, see internal/api/util/robots.go
# More info @ https://developers.google.com/search/docs/crawling-indexing/robots/intro
+1
View File
@@ -189,6 +189,7 @@ func sizeofAccount() uintptr {
FetchedAt: exampleTime,
Locked: util.Ptr(true),
Discoverable: util.Ptr(false),
Indexable: util.Ptr(false),
URI: exampleURI,
URL: exampleURI,
InboxURI: exampleURI,
+1
View File
@@ -398,6 +398,7 @@ func (suite *AccountTestSuite) TestInsertAccountWithDefaults() {
suite.WithinDuration(time.Now(), newAccount.UpdatedAt, 30*time.Second)
suite.True(*newAccount.Locked)
suite.False(*newAccount.Discoverable)
suite.False(*newAccount.Indexable)
}
func (suite *AccountTestSuite) TestGetAccountPinnedStatusesSomeResults() {
+1
View File
@@ -90,6 +90,7 @@ func (suite *BasicTestSuite) TestPutAccountWithBunDefaultFields() {
// to true, which is why we use pointers for bools in the first place
suite.True(*a.Locked)
suite.False(*a.Discoverable)
suite.False(*a.Indexable)
suite.Equal(testAccount.URI, a.URI)
suite.Equal(testAccount.URL, a.URL)
suite.Zero(testAccount.FetchedAt)
@@ -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 migrations
import (
"context"
"reflect"
gtsmodel "code.superseriousbusiness.org/gotosocial/internal/db/bundb/migrations/20251103011557_add_accounts_indexable"
"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 {
// Generate new Account.Indexable column definition from bun.
statusType := reflect.TypeOf((*gtsmodel.Account)(nil))
colDef, err := getBunColumnDef(tx, statusType, "Indexable")
if err != nil {
return err
}
// Add column to Account table. Its default of off is safe.
if _, err = tx.NewAddColumn().
Model((*gtsmodel.Account)(nil)).
ColumnExpr(colDef).
Exec(ctx); // nocollapse
err != nil {
return err
}
return nil
})
}
down := func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
return nil
})
}
if err := Migrations.Register(up, down); err != nil {
panic(err)
}
}
@@ -0,0 +1,283 @@
// 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 (
"crypto/rsa"
"time"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
)
// Account represents either a local or a remote ActivityPub actor.
// https://www.w3.org/TR/activitypub/#actor-objects
type Account struct {
// Database ID of the account.
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
// Datetime when the account was created.
// Corresponds to ActivityStreams `published` prop.
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
// Datetime when was the account was last updated,
// ie., when the actor last sent out an Update
// activity, or if never, when it was `published`.
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
// Datetime when the account was last fetched /
// dereferenced by this GoToSocial instance.
FetchedAt time.Time `bun:"type:timestamptz,nullzero"`
// Username of the account.
//
// Corresponds to AS `preferredUsername` prop, which gives
// no uniqueness guarantee. However, we do enforce uniqueness
// for it as, in practice, it always is and we rely on this.
Username string `bun:",nullzero,notnull,unique:accounts_username_domain_uniq"`
// Domain of the account, discovered via webfinger.
//
// Null if this is a local account, otherwise
// something like `example.org`.
Domain string `bun:",nullzero,unique:accounts_username_domain_uniq"`
// Database ID of the account's avatar MediaAttachment, if set.
AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"`
// MediaAttachment corresponding to AvatarMediaAttachmentID.
AvatarMediaAttachment *gtsmodel.MediaAttachment `bun:"-"`
// URL of the avatar media.
//
// Null for local accounts.
AvatarRemoteURL string `bun:",nullzero"`
// Database ID of the account's header MediaAttachment, if set.
HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"`
// MediaAttachment corresponding to HeaderMediaAttachmentID.
HeaderMediaAttachment *gtsmodel.MediaAttachment `bun:"-"`
// URL of the header media.
//
// Null for local accounts.
HeaderRemoteURL string `bun:",nullzero"`
// Display name for this account, if set.
//
// Corresponds to the ActivityStreams `name` property.
//
// If null, fall back to username for display purposes.
DisplayName string `bun:",nullzero"`
// Database IDs of any emojis used in
// this account's bio, display name, etc
EmojiIDs []string `bun:"emojis,array"`
// Emojis corresponding to EmojiIDs.
Emojis []*gtsmodel.Emoji `bun:"-"`
// A slice of of key/value fields that
// this account has added to their profile.
//
// Corresponds to schema.org PropertyValue types in `attachments`.
Fields []*gtsmodel.Field `bun:",nullzero"`
// The raw (unparsed) content of fields that this
// account has added to their profile, before
// conversion to HTML.
//
// Only set for local accounts.
FieldsRaw []*gtsmodel.Field `bun:",nullzero"`
// A note that this account has on their profile
// (ie., the account's bio/description of themselves).
//
// Corresponds to the ActivityStreams `summary` property.
Note string `bun:",nullzero"`
// The raw (unparsed) version of Note, before conversion to HTML.
//
// Only set for local accounts.
NoteRaw string `bun:",nullzero"`
// ActivityPub URI/IDs by which this account is also known.
//
// Corresponds to the ActivityStreams `alsoKnownAs` property.
AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"`
// Accounts matching AlsoKnownAsURIs.
AlsoKnownAs []*Account `bun:"-"`
// URI/ID to which the account has (or claims to have) moved.
//
// Corresponds to the ActivityStreams `movedTo` property.
//
// Even if this field is set the move may not yet have been
// processed. Check `move` for this.
MovedToURI string `bun:",nullzero"`
// Account matching MovedToURI.
MovedTo *Account `bun:"-"`
// ID of a Move in the database for this account.
// Only set if we received or created a Move activity
// for which this account URI was the origin.
MoveID string `bun:"type:CHAR(26),nullzero"`
// Move corresponding to MoveID, if set.
Move *gtsmodel.Move `bun:"-"`
// True if account requires manual approval of Follows.
//
// Corresponds to AS `manuallyApprovesFollowers` prop.
Locked *bool `bun:",nullzero,notnull,default:true"`
// True if account has opted in to being shown in
// directories and exposed to search engines.
//
// Corresponds to the toot `discoverable` property.
Discoverable *bool `bun:",nullzero,notnull,default:false"`
// True if account has opted into its posts being indexed for full-text search.
//
// Corresponds to the toot `indexable` property.
Indexable *bool `bun:",nullzero,notnull,default:false"`
// ActivityPub URI/ID for this account.
//
// Must be set, must be unique.
URI string `bun:",nullzero,notnull,unique"`
// URL at which a web representation of this
// account should be available, if set.
//
// Corresponds to ActivityStreams `url` prop.
URL string `bun:",nullzero"`
// URI of the actor's inbox.
//
// Corresponds to ActivityPub `inbox` property.
//
// According to AP this MUST be set, but some
// implementations don't set it for service actors.
InboxURI string `bun:",nullzero"`
// URI/ID of this account's sharedInbox, if set.
//
// Corresponds to ActivityPub `endpoints.sharedInbox`.
//
// Gotcha warning: this is a string pointer because
// it has three possible states:
//
// 1. null: We don't know (yet) if actor has a shared inbox.
// 2. empty: We know it doesn't have a shared inbox.
// 3. not empty: We know it does have a shared inbox.
SharedInboxURI *string `bun:""`
// URI/ID of the actor's outbox.
//
// Corresponds to ActivityPub `outbox` property.
//
// According to AP this MUST be set, but some
// implementations don't set it for service actors.
OutboxURI string `bun:",nullzero"`
// URI/ID of the actor's following collection.
//
// Corresponds to ActivityPub `following` property.
//
// According to AP this SHOULD be set.
FollowingURI string `bun:",nullzero"`
// URI/ID of the actor's followers collection.
//
// Corresponds to ActivityPub `followers` property.
//
// According to AP this SHOULD be set.
FollowersURI string `bun:",nullzero"`
// URI/ID of the actor's featured collection.
//
// Corresponds to the Toot `featured` property.
FeaturedCollectionURI string `bun:",nullzero"`
// ActivityStreams type of the actor.
//
// Application, Group, Organization, Person, or Service.
ActorType gtsmodel.AccountActorType `bun:",nullzero,notnull"`
// Private key for signing http requests.
//
// Only defined for local accounts
PrivateKey *rsa.PrivateKey `bun:""`
// Public key for authorizing signed http requests.
//
// Defined for both local and remote accounts
PublicKey *rsa.PublicKey `bun:",notnull"`
// Dereferenceable location of this actor's public key.
//
// Corresponds to https://w3id.org/security/v1 `publicKey.id`.
PublicKeyURI string `bun:",nullzero,notnull,unique"`
// Datetime at which public key will expire/has expired,
// and should be fetched again as appropriate.
//
// Only ever set for remote accounts.
PublicKeyExpiresAt time.Time `bun:"type:timestamptz,nullzero"`
// Datetime at which account was marked as a "memorial",
// ie., user owning the account has passed away.
MemorializedAt time.Time `bun:"type:timestamptz,nullzero"`
// Datetime at which account was set to
// have all its media shown as sensitive.
SensitizedAt time.Time `bun:"type:timestamptz,nullzero"`
// Datetime at which account was silenced.
SilencedAt time.Time `bun:"type:timestamptz,nullzero"`
// Datetime at which account was suspended.
SuspendedAt time.Time `bun:"type:timestamptz,nullzero"`
// ID of the database entry that caused this account to
// be suspended. Can be an account ID or a domain block ID.
SuspensionOrigin string `bun:"type:CHAR(26),nullzero"`
// gtsmodel.AccountSettings for this account.
//
// Local, non-instance-actor accounts only.
Settings *gtsmodel.AccountSettings `bun:"-"`
// gtsmodel.AccountStats for this account.
//
// Local accounts only.
Stats *gtsmodel.AccountStats `bun:"-"`
// True if the actor hides to-public statusables
// from unauthenticated public access via the web.
// Default "false" if not set on the actor model.
HidesToPublicFromUnauthedWeb *bool `bun:",nullzero,notnull,default:false"`
// True if the actor hides cc-public statusables
// from unauthenticated public access via the web.
// Default "true" if not set on the actor model.
HidesCcPublicFromUnauthedWeb *bool `bun:",nullzero,notnull,default:true"`
}
@@ -68,6 +68,7 @@ func (suite *StatusTestSuite) TestDereferenceSimpleStatus() {
suite.NoError(err)
suite.NotNil(account)
suite.True(*account.Discoverable)
suite.True(*account.Indexable)
suite.Equal("https://unknown-instance.com/users/brand_new_person", account.URI)
suite.Equal("hey I'm a new person, your instance hasn't seen me yet uwu", account.Note)
suite.Equal("Geoff Brando New Personson", account.DisplayName)
@@ -105,6 +106,7 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithMention() {
suite.NoError(err)
suite.NotNil(account)
suite.True(*account.Discoverable)
suite.True(*account.Indexable)
suite.Equal("https://unknown-instance.com/users/brand_new_person", account.URI)
suite.Equal("hey I'm a new person, your instance hasn't seen me yet uwu", account.Note)
suite.Equal("Geoff Brando New Personson", account.DisplayName)
@@ -157,6 +159,7 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithTag() {
suite.NoError(err)
suite.NotNil(account)
suite.True(*account.Discoverable)
suite.True(*account.Indexable)
suite.Equal("https://unknown-instance.com/users/brand_new_person", account.URI)
suite.Equal("hey I'm a new person, your instance hasn't seen me yet uwu", account.Note)
suite.Equal("Geoff Brando New Personson", account.DisplayName)
@@ -200,6 +203,7 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithImageAndNoContent() {
suite.NoError(err)
suite.NotNil(account)
suite.True(*account.Discoverable)
suite.True(*account.Indexable)
suite.Equal("https://turnip.farm/users/turniplover6969", account.URI)
suite.Equal("I just think they're neat", account.Note)
suite.Equal("Turnip Lover 6969", account.DisplayName)
+5
View File
@@ -161,6 +161,11 @@ type Account struct {
// Corresponds to the toot `discoverable` property.
Discoverable *bool `bun:",nullzero,notnull,default:false"`
// True if account has opted into its posts being indexed for full-text search.
//
// Corresponds to the toot `indexable` property.
Indexable *bool `bun:",nullzero,notnull,default:false"`
// ActivityPub URI/ID for this account.
//
// Must be set, must be unique.
+1
View File
@@ -558,6 +558,7 @@ func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
account.AlsoKnownAsURIs = nil
account.MovedToURI = ""
account.Discoverable = util.Ptr(false)
account.Indexable = util.Ptr(false)
account.SuspendedAt = now
account.SuspensionOrigin = origin
@@ -66,6 +66,7 @@ func (suite *AccountDeleteTestSuite) TestAccountDeleteLocal() {
suite.Zero(updatedAccount.MemorializedAt)
suite.Empty(updatedAccount.AlsoKnownAsURIs)
suite.False(*updatedAccount.Discoverable)
suite.False(*updatedAccount.Indexable)
suite.WithinDuration(time.Now(), updatedAccount.SuspendedAt, 1*time.Minute)
suite.Equal(suspensionOrigin, updatedAccount.SuspensionOrigin)
+5
View File
@@ -77,6 +77,11 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
acctColumns = append(acctColumns, "discoverable")
}
if form.Indexable != nil {
account.Indexable = form.Indexable
acctColumns = append(acctColumns, "indexable")
}
if bot := form.Bot; bot != nil {
if *bot {
// Mark account as an Application.
+8 -1
View File
@@ -340,7 +340,8 @@ func (p *Processor) WebContextGet(
// Start preparing web context.
wCtx := &apimodel.WebThreadContext{
Statuses: make([]*apimodel.WebStatus, 0, len(wholeThread)),
Statuses: make([]*apimodel.WebStatus, 0, len(wholeThread)),
Indexable: true,
}
var (
@@ -499,6 +500,12 @@ func (p *Processor) WebContextGet(
wCtx.Status = webStatus
}
// If this status isn't indexable, mark the thread as not indexable.
// webStatus.Account from StatusToWebStatus() should always be non-nil.
if !webStatus.Account.Indexable {
wCtx.Indexable = false
}
wCtx.Statuses = append(wCtx.Statuses, webStatus)
}
+111
View File
@@ -20,11 +20,15 @@ package status_test
import (
"testing"
"code.superseriousbusiness.org/gotosocial/internal/ap"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/id"
"code.superseriousbusiness.org/gotosocial/internal/processing/status"
"code.superseriousbusiness.org/gotosocial/internal/util"
"github.com/stretchr/testify/suite"
)
// Test topo sorting of thread replies.
type topoSortTestSuite struct {
suite.Suite
}
@@ -204,3 +208,110 @@ func (suite *topoSortTestSuite) TestNil() {
func TestTopoSortTestSuite(t *testing.T) {
suite.Run(t, &topoSortTestSuite{})
}
// Test properties of web contexts.
type webContextGetTestSuite struct {
StatusStandardTestSuite
indexableAccount1 *gtsmodel.Account
indexableAccount2 *gtsmodel.Account
nonindexableAccount1 *gtsmodel.Account
}
// Find a test account by name with a given indexable setting, or fail if we can't.
func (suite *webContextGetTestSuite) requireIndexableAccount(name string, indexable bool) *gtsmodel.Account {
account := suite.testAccounts[name]
if account == nil || account.Indexable == nil || *account.Indexable != indexable {
suite.FailNowf("expected test account %s's indexable setting to be %v", name, indexable)
}
return account
}
func (suite *webContextGetTestSuite) SetupSuite() {
suite.StatusStandardTestSuite.SetupSuite()
suite.indexableAccount1 = suite.requireIndexableAccount("local_account_1", true)
suite.indexableAccount2 = suite.requireIndexableAccount("admin_account", true)
suite.nonindexableAccount1 = suite.requireIndexableAccount("local_account_2", false)
}
// Create a status in the test DB, or fail if we can't.
func (suite *webContextGetTestSuite) createStatus(account *gtsmodel.Account, visibility gtsmodel.Visibility, inReplyTo *gtsmodel.Status) *gtsmodel.Status {
ctx := suite.T().Context()
statusID := id.NewULID()
newStatus := &gtsmodel.Status{
ID: statusID,
URI: "https://status-id.test/" + statusID,
AccountID: account.ID,
AccountURI: account.URI,
Visibility: visibility,
Local: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
Federated: util.Ptr(true),
}
if inReplyTo != nil {
newStatus.InReplyToID = inReplyTo.ID
newStatus.InReplyToAccountID = inReplyTo.AccountID
}
if err := suite.db.PutStatus(ctx, newStatus); err != nil {
suite.FailNow(err.Error())
}
return newStatus
}
// If all visible statuses in a thread are indexable, so is the thread.
func (suite *webContextGetTestSuite) TestAllVisibleIndexable() {
ctx := suite.T().Context()
op := suite.createStatus(suite.indexableAccount1, gtsmodel.VisibilityPublic, nil)
_ = suite.createStatus(suite.indexableAccount2, gtsmodel.VisibilityPublic, op)
webContext, err := suite.status.WebContextGet(ctx, op.ID)
if err != nil {
suite.FailNow(err.Error())
return
}
suite.Len(webContext.Statuses, 2, "precondition failed")
suite.Equal(0, webContext.ThreadRepliesHidden, "precondition failed")
suite.True(webContext.Indexable)
}
// If any visible statuses in a thread are not indexable, so is the thread.
func (suite *webContextGetTestSuite) TestOneVisibleNonindexable() {
ctx := suite.T().Context()
op := suite.createStatus(suite.indexableAccount1, gtsmodel.VisibilityPublic, nil)
_ = suite.createStatus(suite.indexableAccount2, gtsmodel.VisibilityPublic, op)
_ = suite.createStatus(suite.nonindexableAccount1, gtsmodel.VisibilityPublic, op)
webContext, err := suite.status.WebContextGet(ctx, op.ID)
if err != nil {
suite.FailNow(err.Error())
return
}
suite.Len(webContext.Statuses, 3, "precondition failed")
suite.Equal(0, webContext.ThreadRepliesHidden, "precondition failed")
suite.False(webContext.Indexable)
}
// If the only nonindexable statuses in a thread are also invisible, then the thread is still indexable.
func (suite *webContextGetTestSuite) TestOneInvisibleNonindexable() {
ctx := suite.T().Context()
op := suite.createStatus(suite.indexableAccount1, gtsmodel.VisibilityPublic, nil)
_ = suite.createStatus(suite.indexableAccount2, gtsmodel.VisibilityPublic, op)
_ = suite.createStatus(suite.nonindexableAccount1, gtsmodel.VisibilityUnlocked, op)
webContext, err := suite.status.WebContextGet(ctx, op.ID)
if err != nil {
suite.FailNow(err.Error())
return
}
suite.Len(webContext.Statuses, 2, "precondition failed")
suite.Equal(1, webContext.ThreadRepliesHidden, "precondition failed")
suite.True(webContext.Indexable)
}
func TestWebContextGetTestSuite(t *testing.T) {
suite.Run(t, &webContextGetTestSuite{})
}
@@ -67,6 +67,8 @@ func (suite *NotificationTestSuite) TestStreamNotification() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -78,6 +78,8 @@ func (suite *StatusUpdateTestSuite) TestStreamNotification() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -420,6 +420,7 @@ func (suite *FromFediAPITestSuite) TestProcessAccountDelete() {
suite.Empty(dbAccount.HeaderRemoteURL)
suite.Empty(dbAccount.Fields)
suite.False(*dbAccount.Discoverable)
suite.False(*dbAccount.Indexable)
suite.WithinDuration(time.Now(), dbAccount.SuspendedAt, 30*time.Second)
suite.Equal(dbAccount.ID, dbAccount.SuspensionOrigin)
}
+4 -2
View File
@@ -43,8 +43,8 @@ func (suite *DereferenceTestSuite) TestDerefLocalUser() {
defer resp.Body.Close()
suite.Equal(http.StatusOK, resp.StatusCode)
suite.EqualValues(2109, resp.ContentLength)
suite.Equal("2109", resp.Header.Get("Content-Length"))
suite.EqualValues(2155, resp.ContentLength)
suite.Equal("2155", resp.Header.Get("Content-Length"))
suite.Equal(apiutil.AppActivityLDJSON, resp.Header.Get("Content-Type"))
b, err := io.ReadAll(resp.Body)
@@ -68,6 +68,7 @@ func (suite *DereferenceTestSuite) TestDerefLocalUser() {
"@id": "toot:featured",
"@type": "@id"
},
"indexable": "toot:indexable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#"
}
@@ -92,6 +93,7 @@ func (suite *DereferenceTestSuite) TestDerefLocalUser() {
"url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
},
"inbox": "http://localhost:8080/users/the_mighty_zork/inbox",
"indexable": true,
"manuallyApprovesFollowers": false,
"name": "original zork (he/they)",
"outbox": "http://localhost:8080/users/the_mighty_zork/outbox",
+4
View File
@@ -162,6 +162,10 @@ func (c *Converter) ASRepresentationToAccount(
discoverable := ap.GetDiscoverable(accountable)
acct.Discoverable = &discoverable
// Extract account indexability (default = false).
indexable := ap.GetIndexable(accountable)
acct.Indexable = &indexable
// Extract the URL property.
urls := ap.GetURL(accountable)
if len(urls) == 0 {
+10
View File
@@ -79,6 +79,7 @@ func (suite *ASToInternalTestSuite) TestParsePerson() {
suite.Equal("hey I'm a new person, your instance hasn't seen me yet uwu", acct.Note)
suite.Equal("https://unknown-instance.com/@brand_new_person", acct.URL)
suite.True(*acct.Discoverable)
suite.True(*acct.Indexable)
suite.Equal("https://unknown-instance.com/users/brand_new_person#main-key", acct.PublicKeyURI)
suite.False(*acct.Locked)
}
@@ -101,6 +102,7 @@ func (suite *ASToInternalTestSuite) TestParsePersonWithSharedInbox() {
suite.Equal("I just think they're neat", acct.Note)
suite.Equal("https://turnip.farm/@turniplover6969", acct.URL)
suite.True(*acct.Discoverable)
suite.True(*acct.Indexable)
suite.Equal("https://turnip.farm/users/turniplover6969#main-key", acct.PublicKeyURI)
suite.False(*acct.Locked)
}
@@ -149,6 +151,7 @@ func (suite *ASToInternalTestSuite) TestParseGargron() {
suite.Equal("https://mastodon.social/inbox", *acct.SharedInboxURI)
suite.Equal([]string{"https://tooting.ai/users/Gargron"}, acct.AlsoKnownAsURIs)
suite.Equal(int64(1458086400), acct.CreatedAt.Unix())
suite.True(*acct.Indexable)
}
func (suite *ASToInternalTestSuite) TestParseReplyWithMention() {
@@ -206,6 +209,7 @@ func (suite *ASToInternalTestSuite) TestParseOwncastService() {
suite.Equal("linux audio stuff", acct.Note)
suite.False(*acct.Locked)
suite.True(*acct.Discoverable)
suite.False(*acct.Indexable)
suite.Equal("https://owncast.example.org/federation/user/rgh", acct.URI)
suite.Equal("https://owncast.example.org/federation/user/rgh", acct.URL)
suite.Equal("https://owncast.example.org/federation/user/rgh/inbox", acct.InboxURI)
@@ -615,6 +619,7 @@ func (suite *ASToInternalTestSuite) TestParseHonkAccount() {
suite.Equal("honk.example.org", acct.Domain)
suite.False(*acct.Locked)
suite.False(*acct.Discoverable)
suite.False(*acct.Indexable)
// Store the account representation.
acct.ID = "01HMGRMAVQMYQC3DDQ29TPQKJ3" // <- needs an ID
@@ -634,6 +639,7 @@ func (suite *ASToInternalTestSuite) TestParseHonkAccount() {
suite.Equal("honk.example.org", acct.Domain)
suite.False(*acct.Locked)
suite.False(*acct.Discoverable)
suite.False(*acct.Indexable)
// Check DB version.
dbAcct, err := suite.db.GetAccountByID(ctx, acct.ID)
@@ -651,6 +657,7 @@ func (suite *ASToInternalTestSuite) TestParseHonkAccount() {
suite.Equal("honk.example.org", dbAcct.Domain)
suite.False(*dbAcct.Locked)
suite.False(*dbAcct.Discoverable)
suite.False(*dbAcct.Indexable)
// Update the account.
if err := suite.db.UpdateAccount(ctx, acct); err != nil {
@@ -668,6 +675,7 @@ func (suite *ASToInternalTestSuite) TestParseHonkAccount() {
suite.Equal("honk.example.org", acct.Domain)
suite.False(*acct.Locked)
suite.False(*acct.Discoverable)
suite.False(*acct.Indexable)
// Check DB version.
dbAcct, err = suite.db.GetAccountByID(ctx, acct.ID)
@@ -685,6 +693,7 @@ func (suite *ASToInternalTestSuite) TestParseHonkAccount() {
suite.Equal("honk.example.org", dbAcct.Domain)
suite.False(*dbAcct.Locked)
suite.False(*dbAcct.Discoverable)
suite.False(*dbAcct.Indexable)
// Clear caches.
suite.state.Caches.DB = cache.DBCaches{}
@@ -705,6 +714,7 @@ func (suite *ASToInternalTestSuite) TestParseHonkAccount() {
suite.Equal("honk.example.org", dbAcct.Domain)
suite.False(*dbAcct.Locked)
suite.False(*dbAcct.Discoverable)
suite.False(*dbAcct.Indexable)
}
func (suite *ASToInternalTestSuite) TestParseAccountableWithoutPreferredUsername() {
+6
View File
@@ -198,6 +198,12 @@ func (c *Converter) AccountToAS(
discoverableProp.Set(*a.Discoverable)
accountable.SetTootDiscoverable(discoverableProp)
// indexable
// Account has opted its posts into full-text search features.
indexableProp := streams.NewTootIndexableProperty()
indexableProp.Set(*a.Indexable)
accountable.SetTootIndexable(indexableProp)
// devices
// NOT IMPLEMENTED, probably won't implement
+14
View File
@@ -59,6 +59,7 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
"@id": "toot:featured",
"@type": "@id"
},
"indexable": "toot:indexable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#"
}
@@ -83,6 +84,7 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
"url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
},
"inbox": "http://localhost:8080/users/the_mighty_zork/inbox",
"indexable": true,
"manuallyApprovesFollowers": false,
"name": "original zork (he/they)",
"outbox": "http://localhost:8080/users/the_mighty_zork/outbox",
@@ -130,6 +132,7 @@ func (suite *InternalToASTestSuite) TestAccountToASBot() {
"@id": "toot:featured",
"@type": "@id"
},
"indexable": "toot:indexable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#"
}
@@ -154,6 +157,7 @@ func (suite *InternalToASTestSuite) TestAccountToASBot() {
"url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
},
"inbox": "http://localhost:8080/users/the_mighty_zork/inbox",
"indexable": true,
"manuallyApprovesFollowers": false,
"name": "original zork (he/they)",
"outbox": "http://localhost:8080/users/the_mighty_zork/outbox",
@@ -196,6 +200,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithFields() {
"@id": "toot:featured",
"@type": "@id"
},
"indexable": "toot:indexable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#",
@@ -222,6 +227,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithFields() {
"hidesToPublicFromUnauthedWeb": false,
"id": "http://localhost:8080/users/1happyturtle",
"inbox": "http://localhost:8080/users/1happyturtle/inbox",
"indexable": false,
"manuallyApprovesFollowers": true,
"name": "happy little turtle :3",
"outbox": "http://localhost:8080/users/1happyturtle/outbox",
@@ -276,6 +282,7 @@ func (suite *InternalToASTestSuite) TestAccountToASAliasedAndMoved() {
"@id": "toot:featured",
"@type": "@id"
},
"indexable": "toot:indexable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"movedTo": {
"@id": "as:movedTo",
@@ -307,6 +314,7 @@ func (suite *InternalToASTestSuite) TestAccountToASAliasedAndMoved() {
"url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
},
"inbox": "http://localhost:8080/users/the_mighty_zork/inbox",
"indexable": true,
"manuallyApprovesFollowers": false,
"movedTo": "http://localhost:8080/users/1happyturtle",
"name": "original zork (he/they)",
@@ -352,6 +360,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithOneField() {
"@id": "toot:featured",
"@type": "@id"
},
"indexable": "toot:indexable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#",
@@ -373,6 +382,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithOneField() {
"hidesToPublicFromUnauthedWeb": false,
"id": "http://localhost:8080/users/1happyturtle",
"inbox": "http://localhost:8080/users/1happyturtle/inbox",
"indexable": false,
"manuallyApprovesFollowers": true,
"name": "happy little turtle :3",
"outbox": "http://localhost:8080/users/1happyturtle/outbox",
@@ -416,6 +426,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() {
"@id": "toot:featured",
"@type": "@id"
},
"indexable": "toot:indexable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#"
}
@@ -440,6 +451,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() {
"url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
},
"inbox": "http://localhost:8080/users/the_mighty_zork/inbox",
"indexable": true,
"manuallyApprovesFollowers": false,
"name": "original zork (he/they)",
"outbox": "http://localhost:8080/users/the_mighty_zork/outbox",
@@ -493,6 +505,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() {
"@id": "toot:featured",
"@type": "@id"
},
"indexable": "toot:indexable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#"
}
@@ -520,6 +533,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() {
"url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
},
"inbox": "http://localhost:8080/users/the_mighty_zork/inbox",
"indexable": true,
"manuallyApprovesFollowers": false,
"name": "original zork (he/they)",
"outbox": "http://localhost:8080/users/the_mighty_zork/outbox",
+4
View File
@@ -371,6 +371,7 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
var (
locked = util.PtrOrValue(a.Locked, true)
discoverable = util.PtrOrValue(a.Discoverable, false)
indexable = util.PtrOrValue(a.Indexable, false)
)
// Remaining properties are simple and
@@ -383,6 +384,8 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
DisplayName: a.DisplayName,
Locked: locked,
Discoverable: discoverable,
Indexable: indexable,
NoIndex: !indexable,
Bot: a.ActorType.IsBot(),
CreatedAt: util.FormatISO8601(a.CreatedAt),
Note: a.Note,
@@ -527,6 +530,7 @@ func (c *Converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel.
ID: a.ID,
Username: a.Username,
Acct: acct,
NoIndex: true,
Bot: a.ActorType.IsBot(),
CreatedAt: util.FormatISO8601(a.CreatedAt),
URL: a.URL,
@@ -52,6 +52,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontend() {
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -105,6 +107,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendAliasedAndMoved()
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -152,6 +156,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendAliasedAndMoved()
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -206,6 +212,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiStruct()
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -256,6 +264,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiIDs() {
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -302,6 +312,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() {
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -362,6 +374,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendPublicPunycode()
"display_name": "",
"locked": false,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2020-08-10T12:13:28.000Z",
"note": "",
@@ -402,6 +416,8 @@ func (suite *InternalToFrontendTestSuite) TestLocalInstanceAccountToFrontendPubl
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": true,
"created_at": "2020-05-17T13:10:59.000Z",
"note": "",
@@ -442,6 +458,8 @@ func (suite *InternalToFrontendTestSuite) TestLocalInstanceAccountToFrontendBloc
"display_name": "",
"locked": false,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": true,
"created_at": "2020-05-17T13:10:59.000Z",
"note": "",
@@ -503,6 +521,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -666,6 +686,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendHTMLContentWarning
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -831,6 +853,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendApplicationDeleted
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -987,6 +1011,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendUnknownAttachments
"display_name": "some user",
"locked": true,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2020-08-10T12:13:28.000Z",
"note": "i'm a real son of a gun",
@@ -1180,6 +1206,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
"display_name": "some user",
"locked": true,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2020-08-10T12:13:28.000Z",
"note": "i'm a real son of a gun",
@@ -1318,6 +1346,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendUnknownLanguage()
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -1479,6 +1509,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendPartialInteraction
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -1601,6 +1633,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToAPIStatusPendingApproval()
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -1830,6 +1864,8 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -1990,6 +2026,8 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV2ToFrontend() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -2112,6 +2150,8 @@ func (suite *InternalToFrontendTestSuite) TestReportToFrontend1() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -2157,6 +2197,8 @@ func (suite *InternalToFrontendTestSuite) TestReportToFrontend2() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -2235,6 +2277,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -2282,6 +2326,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -2342,6 +2388,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -2398,6 +2446,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -2477,6 +2527,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -2537,6 +2589,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -2587,6 +2641,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -2762,6 +2818,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontendSuspendedLoca
"display_name": "big gerald",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2021-09-26T10:52:36.000Z",
"note": "i post about like, i dunno, stuff, or whatever!!!!",
@@ -2809,6 +2867,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontendSuspendedLoca
"display_name": "",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "",
@@ -2858,6 +2918,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontendSuspendedLoca
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -2914,6 +2976,8 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontendSuspendedLoca
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -3062,6 +3126,8 @@ func (suite *InternalToFrontendTestSuite) TestIntReqToAPI() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -3120,6 +3186,8 @@ func (suite *InternalToFrontendTestSuite) TestIntReqToAPI() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -3233,6 +3301,8 @@ func (suite *InternalToFrontendTestSuite) TestIntReqToAPI() {
"display_name": "",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-17T13:10:59.000Z",
"note": "",
@@ -3360,6 +3430,8 @@ func (suite *InternalToFrontendTestSuite) TestConversationToAPISelfConvo() {
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -3415,6 +3487,8 @@ func (suite *InternalToFrontendTestSuite) TestConversationToAPISelfConvo() {
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -3533,6 +3607,8 @@ func (suite *InternalToFrontendTestSuite) TestConversationToAPI() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -3596,6 +3672,8 @@ func (suite *InternalToFrontendTestSuite) TestConversationToAPI() {
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"indexable": true,
"noindex": false,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
@@ -3698,6 +3776,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToAPIEdits() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -3743,6 +3823,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToAPIEdits() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -3801,6 +3883,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToAPIEdits() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
@@ -3846,6 +3930,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToAPIEdits() {
"display_name": "happy little turtle :3",
"locked": true,
"discoverable": false,
"indexable": false,
"noindex": true,
"bot": false,
"created_at": "2022-06-04T13:12:00.000Z",
"note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
+2
View File
@@ -187,6 +187,7 @@ func (suite *WrapTestSuite) TestWrapAccountableInUpdate() {
"@id": "toot:featured",
"@type": "@id"
},
"indexable": "toot:indexable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#"
}
@@ -215,6 +216,7 @@ func (suite *WrapTestSuite) TestWrapAccountableInUpdate() {
"url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
},
"inbox": "http://localhost:8080/users/the_mighty_zork/inbox",
"indexable": true,
"manuallyApprovesFollowers": false,
"name": "original zork (he/they)",
"outbox": "http://localhost:8080/users/the_mighty_zork/outbox",
+4 -3
View File
@@ -118,10 +118,11 @@ func (m *Module) prepareProfile(c *gin.Context) *profile {
rssFeed = "/@" + account.Username + "/feed.rss"
}
// Only allow search robots
// if account is discoverable.
// Since we serve the profile and posts together,
// only allow search robots
// if account is discoverable *and* indexable.
var robotsMeta string
if account.Discoverable {
if account.Discoverable && account.Indexable {
robotsMeta = apiutil.RobotsDirectivesAllowSome
}
+8 -1
View File
@@ -118,6 +118,12 @@ func (m *Module) threadGETHandler(c *gin.Context) {
return
}
// If every account in the thread is indexable, then we allow the thread page to be indexed.
var robotsMeta string
if context.Indexable {
robotsMeta = apiutil.RobotsDirectivesAllowSome
}
// Prepare stylesheets for thread.
stylesheets := make([]string, 0, 6)
@@ -162,7 +168,8 @@ func (m *Module) threadGETHandler(c *gin.Context) {
},
},
Extra: map[string]any{
"context": context,
"context": context,
"robotsMeta": robotsMeta,
},
}
+37
View File
@@ -298,6 +298,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
UpdatedAt: TimeMustParse("2020-05-17T13:10:59Z"),
Locked: util.Ptr(false),
Discoverable: util.Ptr(true),
Indexable: util.Ptr(true),
URI: "http://localhost:8080/users/localhost:8080",
URL: "http://localhost:8080/@localhost:8080",
PublicKeyURI: "http://localhost:8080/users/localhost:8080#main-key",
@@ -319,6 +320,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Locked: util.Ptr(false),
Discoverable: util.Ptr(false),
Indexable: util.Ptr(false),
URI: "http://localhost:8080/users/weed_lord420",
URL: "http://localhost:8080/@weed_lord420",
InboxURI: "http://localhost:8080/users/weed_lord420/inbox",
@@ -341,6 +343,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
UpdatedAt: TimeMustParse("2022-05-17T13:10:59Z"),
Locked: util.Ptr(false),
Discoverable: util.Ptr(true),
Indexable: util.Ptr(true),
URI: "http://localhost:8080/users/admin",
URL: "http://localhost:8080/@admin",
PublicKeyURI: "http://localhost:8080/users/admin#main-key",
@@ -368,6 +371,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
UpdatedAt: TimeMustParse("2022-05-20T11:09:18Z"),
Locked: util.Ptr(false),
Discoverable: util.Ptr(true),
Indexable: util.Ptr(true),
URI: "http://localhost:8080/users/the_mighty_zork",
URL: "http://localhost:8080/@the_mighty_zork",
InboxURI: "http://localhost:8080/users/the_mighty_zork/inbox",
@@ -413,6 +417,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Locked: util.Ptr(true),
Discoverable: util.Ptr(false),
Indexable: util.Ptr(false),
URI: "http://localhost:8080/users/1happyturtle",
URL: "http://localhost:8080/@1happyturtle",
InboxURI: "http://localhost:8080/users/1happyturtle/inbox",
@@ -459,6 +464,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
UpdatedAt: TimeMustParse("2025-03-15T11:08:00Z"),
Locked: util.Ptr(false),
Discoverable: util.Ptr(false),
Indexable: util.Ptr(false),
URI: "http://localhost:8080/users/media_mogul",
URL: "http://localhost:8080/@media_mogul",
InboxURI: "http://localhost:8080/users/media_mogul/inbox",
@@ -484,6 +490,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Locked: util.Ptr(false),
Discoverable: util.Ptr(true),
Indexable: util.Ptr(true),
URI: "http://fossbros-anonymous.io/users/foss_satan",
URL: "http://fossbros-anonymous.io/@foss_satan",
InboxURI: "http://fossbros-anonymous.io/users/foss_satan/inbox",
@@ -509,6 +516,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Locked: util.Ptr(true),
Discoverable: util.Ptr(true),
Indexable: util.Ptr(true),
URI: "http://example.org/users/Some_User",
URL: "http://example.org/@Some_User",
InboxURI: "http://example.org/users/Some_User/inbox",
@@ -534,6 +542,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Locked: util.Ptr(true),
Discoverable: util.Ptr(true),
Indexable: util.Ptr(true),
URI: "http://thequeenisstillalive.technology/users/her_fuckin_maj",
URL: "http://thequeenisstillalive.technology/@her_fuckin_maj",
InboxURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/inbox",
@@ -558,6 +567,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Locked: util.Ptr(false),
Discoverable: util.Ptr(false),
Indexable: util.Ptr(false),
URI: "https://xn--xample-ova.org/users/%C3%BCser",
URL: "https://xn--xample-ova.org/users/@%C3%BCser",
FetchedAt: time.Time{},
@@ -3626,6 +3636,7 @@ func NewTestFediPeople() map[string]vocab.ActivityStreamsPerson {
"hey I'm a new person, your instance hasn't seen me yet uwu",
URLMustParse("https://unknown-instance.com/@brand_new_person"),
true,
true,
URLMustParse("https://unknown-instance.com/users/brand_new_person#main-key"),
newPerson1Pub,
nil,
@@ -3651,6 +3662,7 @@ func NewTestFediPeople() map[string]vocab.ActivityStreamsPerson {
"I just think they're neat",
URLMustParse("https://turnip.farm/@turniplover6969"),
true,
true,
URLMustParse("https://turnip.farm/users/turniplover6969#main-key"),
turnipLover6969Pub,
nil,
@@ -3676,6 +3688,7 @@ func NewTestFediPeople() map[string]vocab.ActivityStreamsPerson {
"Peepee poo poo",
URLMustParse("http://example.org/@Some_User"),
true,
true,
URLMustParse("http://example.org/users/Some_User#main-key"),
someUserPub,
nil,
@@ -3701,6 +3714,7 @@ func NewTestFediPeople() map[string]vocab.ActivityStreamsPerson {
"",
URLMustParse("https://shrimpnet.example.org/@shrimp"),
true,
true,
URLMustParse("https://shrimpnet.example.org/users/shrimp#main-key"),
shrimpPub,
URLMustParse("https://shrimpnet.example.org/files/public-1c8468b8-eb2d-485f-9967-f4238ded95e7.webp"),
@@ -3734,6 +3748,7 @@ func NewTestFediGroups() map[string]vocab.ActivityStreamsGroup {
"",
URLMustParse("https://unknown-instance.com/@some_group"),
true,
true,
URLMustParse("https://unknown-instance.com/groups/some_group#main-key"),
newGroup1Pub,
nil,
@@ -3765,6 +3780,7 @@ func NewTestFediServices() map[string]vocab.ActivityStreamsService {
"",
URLMustParse("https://owncast.example.org/federation/user/rgh"),
true,
true,
URLMustParse("https://owncast.example.org/federation/user/rgh#main-key"),
newService1Pub,
nil,
@@ -4437,6 +4453,7 @@ func newAPPerson(
note string,
profileURL *url.URL,
discoverable bool,
indexable bool,
publicKeyURI *url.URL,
pkey *rsa.PublicKey,
avatarURL *url.URL,
@@ -4540,6 +4557,12 @@ func newAPPerson(
discoverableProp.Set(discoverable)
person.SetTootDiscoverable(discoverableProp)
// indexable
// Account has opted its posts into full-text search features.
indexableProp := streams.NewTootIndexableProperty()
indexableProp.Set(indexable)
person.SetTootIndexable(indexableProp)
// devices
// NOT IMPLEMENTED, probably won't implement
@@ -4650,6 +4673,7 @@ func newAPGroup(
note string,
profileURL *url.URL,
discoverable bool,
indexable bool,
publicKeyURI *url.URL,
pkey *rsa.PublicKey,
avatarURL *url.URL,
@@ -4740,6 +4764,12 @@ func newAPGroup(
discoverableProp.Set(discoverable)
group.SetTootDiscoverable(discoverableProp)
// indexable
// Account has opted its posts into full-text search features.
indexableProp := streams.NewTootIndexableProperty()
indexableProp.Set(indexable)
group.SetTootIndexable(indexableProp)
// devices
// NOT IMPLEMENTED, probably won't implement
@@ -4834,6 +4864,7 @@ func newAPService(
note string,
profileURL *url.URL,
discoverable bool,
indexable bool,
publicKeyURI *url.URL,
pkey *rsa.PublicKey,
avatarURL *url.URL,
@@ -4924,6 +4955,12 @@ func newAPService(
discoverableProp.Set(discoverable)
service.SetTootDiscoverable(discoverableProp)
// indexable
// Account has opted its posts into full-text search features.
indexableProp := streams.NewTootIndexableProperty()
indexableProp.Set(indexable)
service.SetTootIndexable(indexableProp)
// devices
// NOT IMPLEMENTED, probably won't implement
@@ -108,6 +108,7 @@ function ProfileForm({ data: profile }: ProfileFormProps) {
bot: useBoolInput("bot", { source: profile }),
locked: useBoolInput("locked", { source: profile }),
discoverable: useBoolInput("discoverable", { source: profile}),
indexable: useBoolInput("indexable", { source: profile}),
enableRSS: useBoolInput("enable_rss", { source: profile }),
hideCollections: useBoolInput("hide_collections", { source: profile }),
webVisibility: useTextInput("web_visibility", { source: profile, valueSelector: (p: Account) => p.source?.web_visibility }),
@@ -314,6 +315,10 @@ function ProfileForm({ data: profile }: ProfileFormProps) {
field={form.discoverable}
label="Mark account as discoverable by search engines and directories."
/>
<Checkbox
field={form.indexable}
label="Mark account's posts as full-text indexable."
/>
<Checkbox
field={form.enableRSS}
label="Enable RSS feed of posts."