[docs] add zh docs (#3507)

* [docs] add zh docs

* [docs] add lang dropdown

* [docs] update mkdocs zh config

* [docs] migrate assets

* [docs] update overrides dir in mkdocs zh config

* [docs] exclude locales director in main mkdocs config

* [docs] rename assets to public to avoid conflicting with template

* [docs] extra_css change followup

* [docs] add theme.palette.toggle.icon back into mkdocs zh config

* [docs] fix zh readme reference + migrate language-specific repo markdown to docs

* [docs] translate remaining repo docs + update reference

* [docs] update zh index.md reference

* [docs/zh] wording alignment
This commit is contained in:
CDN 2024-11-05 13:36:43 +00:00 committed by GitHub
commit 38a08cd25a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
139 changed files with 20407 additions and 24 deletions

View file

@ -0,0 +1,11 @@
# 访问控制
GoToSocial 使用访问控制限制来保护用户和资源免受外站账户和实例的不必要互动。
如[HTTP 签名](#http-signatures)部分所示GoToSocial 要求所有来自外站服务器的传入 `GET``POST` 请求必须签名。未签名的请求将被拒绝,并返回 http 代码 `401 Unauthorized`
访问控制限制通过检查签名的 `keyId` (即谁拥有发出请求的公钥/私钥对)来实现。
首先,会将 `keyId` URI 的主机值与 GoToSocial 实例的已屏蔽(取消联合)的域列表进行检查。如果域名被检测到位于屏蔽列表,则 http 请求将立即以 http 代码 `403 Forbidden` 中止。
接下来GoToSocial 将检查发出 http 请求的公钥所有者与请求目标资源的所有者之间是否存在(任一方向的)屏蔽。如果 GoToSocial 用户屏蔽了发出请求的外站账户,则请求将以 http 代码 `403 Forbidden` 中止。

View file

@ -0,0 +1,368 @@
# 行为体与行为体属性
## 收件箱
GoToSocial 按照 [ActivityPub 规范](https://www.w3.org/TR/activitypub/#inbox),为行为体实现了收件箱。
如规范所述,[外站](https://www.w3.org/TR/activitypub/#delivery) 应通过向活动目标受众的每个收件箱发送 HTTP POST 请求,将活动传送到 GoToSocial 服务器。
GoToSocial 帐号目前没有实现 [共享收件箱](https://www.w3.org/TR/activitypub/#shared-inbox-delivery) 端点,但这可能会有所改变。当 GoToSocial 服务器上有多个行为体是活动受众时,对已传送活动的去重由 GoToSocial 处理。
对 GoToSocial 行为体收件箱的 POST 请求必须由发起活动的行为体进行正确地 [HTTP 签名](#http-signatures)。
可被接受的收件箱 POST `Content-Type` 头为:
- `application/activity+json`
- `application/activity+json; charset=utf-8`
- `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
未使用上述 `Content-Type` 头之一的收件箱 POST 请求将被拒绝,并返回 HTTP 状态码 [406 - Not Acceptable](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406)。
有关可接受内容类型的更多信息,请参阅 ActivityPub 协议的 [服务器间交互](https://www.w3.org/TR/activitypub/#server-to-server-interactions) 部分。
对格式正确且已签名的收件箱 POST 请求GoToSocial 将返回 HTTP 状态码 [202 - Accepted](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202)。
对格式错误的收件箱 POST 请求,将返回 HTTP 状态码 [400 - Bad Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400)。响应正文可能包含有关 GoToSocial 服务器为何认为请求内容格式错误的更多信息。对于代码 `400` 的回应,其他服务器不应重试交付活动。
即使 GoToSocial 返回 `202` 状态码也可能不继续处理已传送的活动具体取决于活动的发起者、目标和活动类型。ActivityPub 是一个广泛的协议GoToSocial 并未涵盖每种活动和对象的组合。
## 发件箱
GoToSocial 按照 [ActivityPub 规范](https://www.w3.org/TR/activitypub/#outbox),为行为体(即实例账户)实现了发件箱。
要获取某行为体最近发布的活动 [OrderedCollection](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollection),外站可以对用户的发件箱进行 `GET` 请求。其地址类似于 `https://example.org/users/whatever/outbox`
服务器将返回以下结构的 OrderedCollection
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/whatever/outbox",
"type": "OrderedCollection",
"first": "https://example.org/users/whatever/outbox?page=true"
}
```
请注意,`OrderedCollection` 本身不包含项目。调用者必须解引用 `first` 页面以开始获取项目。例如,对 `https://example.org/users/whatever/outbox?page=true``GET` 请求将生成如下内容:
```json
{
"id": "https://example.org/users/whatever/outbox?page=true",
"type": "OrderedCollectionPage",
"next": "https://example.org/users/whatever/outbox?max_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true",
"prev": "https://example.org/users/whatever/outbox?min_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true",
"partOf": "https://example.org/users/whatever/outbox",
"orderedItems": [
{
"id": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7/activity",
"type": "Create",
"actor": "https://example.org/users/whatever",
"published": "2021-10-18T20:06:18Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://example.org/users/whatever/followers"
],
"object": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7"
}
]
}
```
`orderedItems` 数组最多包含 30 个条目。要获取超过此数量的条目,调用者可以使用响应中提供的 `next` 链接。
请注意,在返回的 `orderedItems` 中,所有活动类型都将是 `Create`。在每个活动中,`object` 字段将是由拥有发件箱的行为体创建的原始公共贴文的 AP URI`Note``to` 字段中包含 `https://www.w3.org/ns/activitystreams#Public`,且不是对另一个贴文的回复)。调用者可以使用返回的 AP URI 来解引用这些 `Note` 的内容。
## 粉丝与关注集合
GoToSocial 将粉丝和关注的集合实现为 `OrderedCollection`。例如,对行为体的关注集合进行正确签名的 `GET` 请求将返回如下内容:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"first": "https://example.org/users/someone/following?limit=40",
"id": "https://example.org/users/someone/following",
"totalItems": 397,
"type": "OrderedCollection"
}
```
从这里开始,你可以使用 `first` 页面开始获取项目。例如,对 `https://example.org/users/someone/following?limit=40``GET` 请求将产生如下内容:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/someone/following?limit=40",
"next": "https://example.org/users/someone/following?limit=40&max_id=01V1AY4ZJT4JK1NT271SH2WMGH",
"orderedItems": [
"https://example.org/users/someone_else",
"https://somewhere.else.example.org/users/another_account",
[... 38 more entries here ...]
],
"partOf": "https://example.org/users/someone/following",
"prev": "https://example.org/users/someone/following?limit=40&since_id=021HKBY346X7BPFYANPPJN493P",
"totalItems": 397,
"type": "OrderedCollectionPage"
}
```
然后,你可以使用 `next``prev` 端点在 OrderedCollection 中向下和向上翻页。
## 个人资料字段
与 Mastodon 和其他联邦宇宙软件类似GoToSocial 允许用户在其个人资料上设置键/值对;这对于传达简短的信息如链接、代词、年龄等很有用。
为了与其他实现兼容GoToSocial 使用与 Mastodon 相同的 schema.org PropertyValue 扩展,作为设置字段的行为体上的 `attachment` 数组值。例如,以下 JSON 显示了两个 PropertyValue 字段的账户:
```json
{
"@context": [
"http://joinmastodon.org/ns",
"https://w3id.org/security/v1",
"https://www.w3.org/ns/activitystreams",
"http://schema.org"
],
"attachment": [
{
"name": "接受关注",
"type": "PropertyValue",
"value": "纯看个人心情"
},
{
"name": "年龄",
"type": "PropertyValue",
"value": "120"
}
],
"discoverable": false,
"featured": "http://example.org/users/flyingsloth/collections/featured",
"followers": "http://example.org/users/flyingsloth/followers",
"following": "http://example.org/users/flyingsloth/following",
"id": "http://example.org/users/flyingsloth",
"inbox": "http://example.org/users/flyingsloth/inbox",
"manuallyApprovesFollowers": true,
"name": "飞翔的树懒 :3",
"outbox": "http://example.org/users/flyingsloth/outbox",
"preferredUsername": "flyingsloth",
"publicKey": {
"id": "http://example.org/users/flyingsloth#main-key",
"owner": "http://example.org/users/flyingsloth",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtTc6Jpg6LrRPhVQG4KLz\n2+YqEUUtZPd4YR+TKXuCnwEG9ZNGhgP046xa9h3EWzrZXaOhXvkUQgJuRqPrAcfN\nvc8jBHV2xrUeD8pu/MWKEabAsA/tgCv3nUC47HQ3/c12aHfYoPz3ufWsGGnrkhci\nv8PaveJ3LohO5vjCn1yZ00v6osMJMViEZvZQaazyE9A8FwraIexXabDpoy7tkHRg\nA1fvSkg4FeSG1XMcIz2NN7xyUuFACD+XkuOk7UqzRd4cjPUPLxiDwIsTlcgGOd3E\nUFMWVlPxSGjY2hIKa3lEHytaYK9IMYdSuyCsJshd3/yYC9LqxZY2KdlKJ80VOVyh\nyQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"summary": "\u003cp\u003e只是一只普通树懒\u003c/p\u003e",
"tag": [],
"type": "Person",
"url": "http://example.org/@flyingsloth"
}
```
对于没有 `PropertyValue` 字段的行为体,`attachment` 属性将不设置。即,`attachment` 键值不会在行为体中出现(即使是空数组或 null 值也不会)。
尽管 `attachment` 在规范上不是一个有序集合GoToSocial还是为了与其他实现保持一致仍会按应显示的顺序呈现 `attachment``PropertyValue` 字段。
GoToSocial 还将解析 GoToSocial 实例发现的外站行为体的 PropertyValue 字段,以便可以把它们展示给 GoToSocial 实例上的用户。
GoToSocial 默认允许设置最多 6 个 `PropertyValue` 字段,而 Mastodon 的默认值为 4 个。
## 置顶/特色贴文
GoToSocial 允许用户在他们的个人资料上置顶贴文。
在 ActivityPub 术语中GoToSocial 在行为体的 [featured](https://docs.joinmastodon.org/spec/activitypub/#featured) 字段中指定的端点提供这些置顶贴文,格式为 [OrderedCollection](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollection) 。该字段的值将被设置为类似 `https://example.org/users/some_user/collections/featured`
通过向此端点发送经过签名的 GET 请求,外站实例可以解引用特色贴文集合,这将返回带有 `orderedItems` 字段,其中包含贴文 URI 列表的 `OrderedCollection`
置顶多条 `Note` 的用户的 featured collection 示例:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/some_user/collections/featured",
"orderedItems": [
"https://example.org/users/some_user/statuses/01GS7VTYH0S77NNXTP6W4G9EAG",
"https://example.org/users/some_user/statuses/01GSFY2SZK9TPCJFQ1WCCPGDRT",
"https://example.org/users/some_user/statuses/01GSCXY70MZCBFMH5EKJW9ENC8"
],
"totalItems": 3,
"type": "OrderedCollection"
}
```
置顶单条 `Note` 的用户的 featured collection 示例:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/some_user/collections/featured",
"orderedItems": [
"https://example.org/users/some_user/statuses/01GS7VTYH0S77NNXTP6W4G9EAG"
],
"totalItems": 1,
"type": "OrderedCollection"
}
```
没有置顶 `Note` 的示例:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/some_user/collections/featured",
"orderedItems": [],
"totalItems": 0,
"type": "OrderedCollection"
}
```
与 Mastodon 和一些其他实现不同GoToSocial 不会将在 `orderedItems` 的值中提供完整的 `Note` 表示。相反,它仅提供每个 `Note` 的 URI外站服务器可以自行进一步解引用如果它们已经在本地缓存了该 `Note` 则可以不执行此操作)。
作为集合一部分提供的一些 URI 可能指向仅限粉丝可见性的贴文,请求的 `Actor` 不一定有权限查看。外站服务器应确保进行适当的过滤(与其他任何类型的贴文一样),以确保这些贴文仅显示给有权查看的用户。
GoToSocial 和其他服务器实现之间的另一个区别是当用户置顶或取消置顶贴文时GoToSocial 不会向外站服务器发送更新。Mastodon 会通过发送 [Add](https://www.w3.org/TR/activitypub/#add-activity-inbox) 和 [Remove](https://www.w3.org/TR/activitypub/#remove-activity-inbox) 活动类型来进行,`object` 是被置顶或取消置顶的贴文,`target` 是发送 `Actor``featured` 集合。尽管在概念上这很合理,但这与 ActivityPub 协议建议不一致,因为活动的 `target`“不属于接收服务器,因此他们不能更新它”。
相反,建议外站仅定期轮询 GoToSocial 行为体的 `featured` 集合,并根据需要在其缓存表示中添加/删除贴文,以构建和更新 GoToSocial 用户置顶贴文的视图。
## 行为体迁移 / 别名
GoToSocial 支持通过 `Move` 活动以及行为体对象属性 `alsoKnownAs``movedTo` 从一个实例/服务器迁移到另一个。
### `alsoKnownAs`
GoToSocial 支持使用 `alsoKnownAs` 行为体属性进行帐户别名,这是一个 [公认的 ActivityPub 扩展](https://www.w3.org/wiki/Activity_Streams_extensions#as:alsoKnownAs_property)。
#### 传入
在传入的 AP 消息中GoToSocial 将查找行为体上的 `alsoKnownAs` 属性,该属性是行为体也可以通过的其他活动 IDs/URIs 构成的数组。
例如:
```json
{
"@context": [
"http://joinmastodon.org/ns",
"https://w3id.org/security/v1",
"https://www.w3.org/ns/activitystreams",
"http://schema.org"
],
"featured": "http://example.org/users/flyingsloth/collections/featured",
"followers": "http://example.org/users/flyingsloth/followers",
"following": "http://example.org/users/flyingsloth/following",
"id": "http://example.org/users/flyingsloth",
"inbox": "http://example.org/users/flyingsloth/inbox",
"manuallyApprovesFollowers": true,
"name": "飞翔的树懒 :3",
"outbox": "http://example.org/users/flyingsloth/outbox",
"preferredUsername": "flyingsloth",
"publicKey": {...},
"summary": "\u003cp\u003e只是一只普通树懒\u003c/p\u003e",
"type": "Person",
"url": "http://example.org/@flyingsloth",
"alsoKnownAs": [
"https://another-server.com/users/flyingsloth",
"https://somewhere-else.org/users/originalsloth"
]
}
```
在上述 AP JSON 中,行为体 `http://example.org/users/flyingsloth` 已设置别名为其他行为体 `https://another-server.com/users/flyingsloth``https://somewhere-else.org/users/originalsloth`
GoToSocial 将传入的 `alsoKnownAs` URI 存储在数据库中,但(当前)不会使用它们,除非用于验证 `Move` 活动(见下文)。
#### 传出
GoToSocial 用户可以通过 GoToSocial 客户端 API 在其账户上设置多个 `alsoKnownAs` URI。GoToSocial 会在存入数据库并在传出 AP 消息序列化之前验证这些 `alsoKnownAs` 别名是否为有效的行为体 URI。
然而GoToSocial 并不验证用户在设置别名之前对那些 `alsoKnownAs` URI 的*所有权*;它期望外站自行进行验证,然后再采信任何传入的 `alsoKnownAs` 值。
例如GoToSocial 实例中的用户 `http://example.org/users/flyingsloth` 可能会在他们的账户上设置 `alsoKnownAs: [ "https://unrelated-server.com/users/someone_else" ]`GoToSocial 会如实传输此别名到其他服务器。
在这种情况下,`https://unrelated-server.com/users/someone_else` 或许不是 `flyingsloth``flyingsloth` 可能无意或恶意地设置了此别名。为了正确验证 `someone_else` 的所有权,外站应检查行为体 `https://unrelated-server.com/users/someone_else``alsoKnownAs` 属性是否包含 `http://example.org/users/flyingsloth` 条目。
换句话说,外站不应默认信任 `alsoKnownAs` 别名,而应确保在将别名视为有效之前,行为体之间存在**双向别名**。
### `movedTo`
GoToSocial 使用 `movedTo` 属性标记账户已迁移。与 `alsoKnownAs` 不同,这不是一个被接受的 ActivityPub 扩展,但它已被 Mastodon 广泛推广,也在 `Move` 活动中使用。[参见 Mastodon 文档获取更多信息](https://documentation.sig.gy/spec/activitypub/#namespaces)。
#### 传入
对于传入的 AP 消息GoToSocial 查找行为体上的 `movedTo` 属性,该属性设置为单个 ActivityPub 行为体 URI/ID。
例如:
```json
{
"@context": [
"http://joinmastodon.org/ns",
"https://w3id.org/security/v1",
"https://www.w3.org/ns/activitystreams",
"http://schema.org"
],
"featured": "http://example.org/users/flyingsloth/collections/featured",
"followers": "http://example.org/users/flyingsloth/followers",
"following": "http://example.org/users/flyingsloth/following",
"id": "http://example.org/users/flyingsloth",
"inbox": "http://example.org/users/flyingsloth/inbox",
"manuallyApprovesFollowers": true,
"name": "飞翔的树懒 :3",
"outbox": "http://example.org/users/flyingsloth/outbox",
"preferredUsername": "flyingsloth",
"publicKey": {...},
"summary": "\u003cp\u003e只是一只普通树懒\u003c/p\u003e",
"type": "Person",
"url": "http://example.org/@flyingsloth",
"alsoKnownAs": [
"https://another-server.com/users/flyingsloth"
],
"movedTo": "https://another-server.com/users/flyingsloth"
}
```
在上述 JSON 中,行为体 `http://example.org/users/flyingsloth` 已设置别名为行为体 `https://another-server.com/users/flyingsloth` 并已迁移/转向该账户。
GoToSocial 将传入的 `movedTo` 值存储在数据库中,但除非行为体在进行移动之前发送了一个 `Move` 活动,否则不会认为帐户迁移已处理(见下文)。
### `Move` 活动
为了实际触发账户迁移GoToSocial 使用 `Move` 活动,并将行为体 URI 作为对象和目标,例如:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/flyingsloth/moves/01HR9FDFCAGM7JYPMWNTFRDQE9",
"actor": "https://example.org/users/flyingsloth",
"type": "Move",
"object": "https://example.org/users/flyingsloth",
"target": "https://another-server.com/users/my_new_account_hurray",
"to": "https://example.org/users/flyingsloth/followers"
}
```
在上述 `Move` 中,行为体 `https://example.org/users/flyingsloth` 指示其账户正在迁移到 URI `https://another-server.com/users/my_new_account_hurray`
### 迁入
在收到行为体收件箱中的 `Move` 活动时GoToSocial 将首先通过以下检查验证 `Move`
1. 请求由 `actor` 签名。
2. `actor``object` 字段相同(你不能迁移其他人的账户)。
3. `actor` 尚未迁移到其他地方。
4. `target` 是有效的行为体 URI可检索、未封禁、未迁移且不在接收到此 `Move` 的 GoToSocial 实例屏蔽的实例上。
5. `target``alsoKnownAs` 设置为发送 `Move``actor`。在此示例中,`https://another-server.com/users/my_new_account_hurray` 必须具有 `alsoKnownAs` 值,其中包括 `https://example.org/users/flyingsloth`
如果检查通过,则 GoToSocial 将通过将粉丝重定向到新账户来处理 `Move`
1. 选择此 GoToSocial 实例上执行 `Move``actor` 的所有粉丝。
2. 对于以这种方式选择的每个本站粉丝,从该粉丝的账户发送关注请求到 `Move``target`
3. 删除针对“旧” `actor` 的所有关注。
这样做的最终结果是,在接收实例上 `https://example.org/users/flyingsloth` 的所有粉丝现在将关注 `https://another-server.com/users/my_new_account_hurray`
GoToSocial 还会删除由执行 `Move``actor` 拥有的所有关注和待关注请求;由 `target` 帐户再次发送关注请求。
为了防止潜在的 DoS 向量GoToSocial 对 `Move` 强制进行 7 天冷却期。一旦帐户成功转移GoToSocial 将在上次迁移后的 7 天内不处理来自新帐户的进一步迁移活动。
#### 迁出
发送帐户迁移时GoToSocial 以类似方式使用 `Move` 活动。当 GoToSocial 实例上的行为体想要执行 `Move`GoToSocial 将首先检查和验证 `Move` 目标,并确保它具有等于执行 `Move` 的行为体的 `alsoKnownAs` 条目。在成功验证后,将向所有发起迁移的行为体的粉丝发送 `Move` 活动,为其指示 `Move``target`。GoToSocial 期望外站将相应的追随者迁移到 `target` 名下。

View file

@ -0,0 +1,73 @@
# 术语表
本文档描述了有关联合的一些常用术语。
## `ActivityPub`
一种基于 ActivityStreams 数据格式的去中心化社交网络协议。参见 [这里](https://www.w3.org/TR/activitypub/)。
GoToSocial 在 与其它 GtS 服务器和其它联邦宇宙服务器(如 Mastodon, Pixelfed 等)通信时使用 ActivityPub 协议。
## `ActivityStreams`
使用 JSON 表示潜在活动和已完成活动的模型/数据格式。参见 [这里](https://www.w3.org/TR/activitystreams-core/)。
GoToSocial 使用 ActivityStreams 数据模型与其他服务器进行 ActivityPub 通信。
## `Actor` (行为体)
Actor 是一个可以执行某些活动(例如关注、点赞、创建贴文、转发等)的 ActivityStreams 对象。参见 [这里](https://www.w3.org/TR/activitypub/#actors)。
在 GoToSocial 中,每个账号/用户都是一个 actor。
## `Dereference` (解引用)
“解引用”一个贴文或账户意味着向托管该贴文或账户的服务器发出 HTTP 请求,以获取其 ActivityStreams 表示。
GoToSocial 对外站服务器解引用贴文和账户,以将它们转换为 GoToSocial 可以理解和处理的模型。
以下是一些示例的详细说明:
假设有人在 ActivityPub 服务器上搜索用户名 `@tobi@goblin.technology`
他们的服务器会在 `goblin.technology` 上通过以下 URL 进行 webfinger 查询:
```text
https://goblin.technology/.well-known/webfinger?resource=acct:tobi@goblin.technology
```
`goblin.technology` 服务器会返回一些 JSON 作为响应,类似于:
```json
{
"subject": "acct:tobi@goblin.technology",
"aliases": [
"https://goblin.technology/users/tobi",
"https://goblin.technology/@tobi"
],
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://goblin.technology/@tobi"
},
{
"rel": "self",
"type": "application/activity+json",
"href": "https://goblin.technology/users/tobi"
}
]
}
```
在链接部分,请求服务器会寻找类型为 `application/activity+json` 的链接,这表示用户的 ActivityStreams 表示。在此情况下URL 是:
```text
https://goblin.technology/users/tobi
```
上述 URL 是 `tobi``goblin.technology` 实例上的 用户/行为体 的 activitypub 表示的*引用*。它之所以被称为引用,是因为它不包含关于该用户的所有信息,只是信息所在位置的参考点。
现在,请求服务器将向该 URL 发送请求,以获得 `@tobi@goblin.technology` 的更完整表示,以符合 ActivityPub 规范。换句话说,服务器现在通过一个*引用*来获取它所引用的内容。这使得它*不再是一个引用*,因此称为*解引用*。
作为类比,考虑在书的目录中查找某些内容时的情况:首先你获得你感兴趣的材料所在的页码,这是一个引用。然后你翻到引用的页面查看内容,这就是解引用。

View file

@ -0,0 +1,85 @@
# HTTP 签名
GoToSocial 要求所有发送到 ActivityPub 服务器的 `GET``POST` 请求都必须附带有效的 HTTP 签名。
GoToSocial 也会为其向其他服务器发送的所有 `GET``POST` 请求签名。
这种行为与 Mastodon 的 [AUTHORIZED_FETCH / "安全模式"](https://docs.joinmastodon.org/admin/config/#authorized_fetch) 等效。
GoToSocial 使用 [superseriousbusiness/httpsig](https://github.com/superseriousbusiness/httpsig) 库(从 go-fed 派生)来为发出的请求签名,并解析和验证传入请求的签名。该库严格遵循 [Cavage HTTP Signature RFC](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12),这是其他实现(如 Mastodon、Pixelfed、Akkoma/Pleroma 等)使用的同一份 RFC。此 RFC 后来被 [httpbis HTTP Signature RFC](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures) 取代,但尚未广泛实施。)
## 查询参数
关于是否应该在用于生成和验证签名的 URL 中包含查询参数HTTP 签名规范并无明确规定。
在历史上GoToSocial 在签名中包含了查询参数,而大多数其他实现则没有。这导致在对 Collection 端点进行签名 GET 请求或验证签名的 GET 请求时(通常使用查询参数进行分页),出现了兼容性问题。
从 0.14 开始GoToSocial 尝试同时签署和验证携带和不携带查询参数请求,以确保与其他实现更好的兼容性。
发送请求时GtS 将首先尝试包含查询参数的情况。当收到外站服务器的 `401` 响应时,它将尝试在不包含查询参数的情况下重新发送请求。
接收请求时GtS 将首先尝试验证包含查询参数的签名。如果签名验证失败,它将尝试在不包含查询参数的情况下重新验证签名。
详细信息请参见 [#894](https://github.com/superseriousbusiness/gotosocial/issues/894)。
## 传入请求
GoToSocial 的请求签名验证在 [internal/federation](https://github.com/superseriousbusiness/gotosocial/blob/main/internal/federation/authenticate.go) 中实现。
GoToSocial 将尝试按以下算法顺序解析签名,成功后将停止:
```text
RSA_SHA256
RSA_SHA512
ED25519
```
## 发出请求
GoToSocial 的请求签名在 [internal/transport](https://github.com/superseriousbusiness/gotosocial/blob/main/internal/transport/signing.go) 中实现。
一旦解决了 https://github.com/superseriousbusiness/gotosocial/issues/2991 GoToSocial 将使用 `(created)` 伪标头代替 `date`
然而,目前在组装签名时:
- 发出的 `GET` 请求使用 `(request-target) host date`
- 发出的 `POST` 请求使用 `(request-target) host date digest`
GoToSocial 在签名中将 `algorithm` 字段设置为 `hs2019`,这一般意味着“从与 keyId 关联的元数据中导出算法”。生成签名时使用的实际算法是 `RSA_SHA256`,这与其他 ActivityPub 实现一致。在验证 GoToSocial 的 HTTP 签名时,外站服务器可以安全地假设该签名是使用 `sha256` 生成的。
## 特点
GoToSocial 在 `Signature` 标头中使用的 `keyId` 格式如下所示:
```text
https://example.org/users/example_user/main-key
```
这不同于大多数其他实现,它们通常在 `keyId` URI 中使用片段 (`#`)。例如,在 Mastodon 上,用户的密钥会这样表示:
```text
https://example.org/users/example_user#main-key
```
对于 Mastodon用户的公钥作为该用户的 Actor 表示的一部分提供。GoToSocial 在提供用户的公钥时模仿了这种行为,但并不在 `main-key` 端点返回完整的 Actor这可能包含敏感字段而是仅返回 Actor 的部分存根。它如下所示:
```json
{
"@context": [
"https://w3id.org/security/v1",
"https://www.w3.org/ns/activitystreams"
],
"id": "https://example.org/users/example_user",
"preferredUsername": "example_user",
"publicKey": {
"id": "https://example.org/users/example_user/main-key",
"owner": "https://example.org/users/example_user",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGB3yDvMl+8p+ViutVRG\nVDl9FO7ZURYXnwB3TedSfG13jyskoiMDNvsbLoUQM9ajZPB0zxJPZUlB/W3BWHRC\nNFQglE5DkB30GjTClNZoOrx64vLRT5wAEwIOjklKVNk9GJi1hFFxrgj931WtxyML\nBvo+TdEblBcoru6MKAov8IU4JjQj5KUmjnW12Rox8dj/rfGtdaH8uJ14vLgvlrAb\neQbN5Ghaxh9DGTo1337O9a9qOsir8YQqazl8ahzS2gvYleV+ou09RDhS75q9hdF2\nLI+1IvFEQ2ZO2tLk3umUP1ioa+5CWKsWD0GAXbQu9uunAV0VoExP4+/9WYOuP0ei\nKwIDAQAB\n-----END PUBLIC KEY-----\n"
},
"type": "Person"
}
```
与 GoToSocial 联合的外站服务器应从 `publicKey` 字段提取公钥。然后,它们应该使用公钥的 `owner` 字段签名 `GET` 请求,进一步解引用 Actor 的完整版本。
这种行为是为了避免外站服务器对完整 Actor 端点进行未签名的 `GET` 请求引入的。然而,由于不合规且引发问题,此行为可能会在未来发生变化。在 [此问题](https://github.com/superseriousbusiness/gotosocial/issues/1186) 中进行跟踪。

View file

@ -0,0 +1,7 @@
# 概述
本节文档包含与 GoToSocial 联合所需的各个 (ActivityPub/ActivityStreams) 元素的信息。
!!! info "Post 与 Status 的区别"
在文档中post 与 status 这两个术语可以互换使用,指的是由用户创建的相同内容:一个(微型)博客条目,可能包含文本、媒体附件等。在中文文档中,我们统一使用“贴文”一词来指代这种内容。

View file

@ -0,0 +1,41 @@
# 管理
## 举报 / 标记
与其他微博 ActivityPub 实现类似GoToSocial 使用 [Flag](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-flag) 活动类型来向其他服务器传达用户举报信息。
### 发送举报
发送的 GoToSocial `Flag` 的 JSON 格式如下:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "http://example.org/users/example.org",
"content": "此用户频繁骚扰其它用户",
"id": "http://example.org/reports/01GP3AWY4CRDVRNZKW0TEAMB5R",
"object": [
"http://fossbros-anonymous.io/users/foss_satan",
"http://fossbros-anonymous.io/users/foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M"
],
"type": "Flag"
}
```
`Flag``actor` 始终是创建该 `Flag` 的 GoToSocial 实例的实例行为体。这样做是为了保护创建举报的用户,实现部分的匿名性,以防止他们成为骚扰目标。
`Flag``content` 是由创建 `Flag` 的用户提交的一段文本,该文本应该向外站管理员提供创建举报的理由。如果用户没有提交理由,`content` 可能是空字符串,也可能不存在于 JSON 中。
`Flag``object` 字段的值可以是一个字符串(被举报用户的 ActivityPub `id`),或者是一个字符串数组,其中数组的第一个条目是被举报用户的 `id`,后续条目是一个或多个被举报的 `Note` / 贴文的 `id`
`Flag` 活动会原样发送到被举报用户的 `inbox`(或共享收件箱)。它不会被包装在 `Create` 活动中。
### 接收举报
GoToSocial 假设接收到的举报会作为 `Flag` 活动发送到被举报用户的 `inbox`。它将按照创建发送 `Flag` 的相同方式解析接收到的 `Flag`,但有一个不同之处:它会尝试从 `object` 字段和 Misskey/Calckey 格式的 `content` 值中解析贴文的 URL这些值包含内联的贴文 URL。
GoToSocial 不会假设接收到的 `Flag` 活动中会设置 `to` 字段。相反,它假定外站使用 `bto``Flag` 发送给其接收者。
接收到的有效 `Flag` 活动将作为举报提供给接收到举报的 GoToSocial 实例管理员,以便他们对被举报用户采取必要的管理措施。
除非 GtS 管理员选择通过其他渠道与被举报用户分享此信息,被举报用户本人不会看到举报信息,也不会收到被举报的通知。

View file

@ -0,0 +1,924 @@
# 帖文及其属性
## 话题标签
GoToSocial 用户可以在贴文中包含话题标签,用于向其他实例表明该用户希望将其贴文与其他使用相同话题标签的贴文加入同一分组,以便于发现。
GoToSocial 默认期望只有公开地址的贴文会按话题标签分组,这与其他 ActivityPub 服务器实现一致。
为了实现话题标签的联合GoToSocial 在对象的 `tag` 属性中使用被广泛采用的 [ActivityStreams `Hashtag` 类型扩展](https://www.w3.org/wiki/Activity_Streams_extensions#as:Hashtag_type)。
以下是一条外发信息中的 `tag` 属性示例,包含自定义表情和一个话题标签:
```json
"tag": [
{
"icon": {
"mediaType": "image/png",
"type": "Image",
"url": "https://example.org/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png"
},
"id": "https://example.org/emoji/01F8MH9H8E4VG3KDYJR9EGPXCQ",
"name": ":rainbow:",
"type": "Emoji",
"updated": "2021-09-20T10:40:37Z"
},
{
"href": "https://example.org/tags/welcome",
"name": "#welcome",
"type": "Hashtag"
}
]
```
当仅有一个话题标签时,`tag` 属性将是一个对象而非数组,如下所示:
```json
"tag": {
"href": "https://example.org/tags/welcome",
"name": "#welcome",
"type": "Hashtag"
}
```
### 话题标签的 `href` 属性
GoToSocial 外发话题标签提供的 `href` URL 指向一个提供 `text/html` 的网页。
GoToSocial 对给定 `text/html` 的内容不做任何保证,外站不应该将 URL 解释为规范的 ActivityPub ID/URI 属性。`href` URL 仅作为可能包含该话题标签更多信息的一个端点。
## 提及
GoToSocial 用户可以在贴文中使用 `@[用户名]@[域名]` 格式提及其他用户。例如,如果一个 GoToSocial 用户想提及实例 `example.org` 上的用户 `someone`,可以在贴文中包含 `@someone@example.org`
!!! info "提及与活动地址"
提及的表示不仅仅是美观问题,它们也会影响活动的地址。
如果 GoToSocial 用户在 Note 中明确提及另一个用户,该用户的 URI 将始终包含在 Note 的 Create 活动的 `To``Cc` 属性中。
如果 Note 的可见性为私信(即,不发送给公众或粉丝),每个提及的目标 URI 将位于活动包装的 `To` 属性中。
在所有其他情况下,提及将包含在活动包装的 `Cc` 属性中。
### 外发
当 GoToSocial 用户提及另一个账户时,提及会作为 `tag` 属性中的一个条目包含在外发的联合消息中。
例如,一个 GoToSocial 实例上的用户提及 `@someone@example.org`,外发 Note 的 `tag` 属性可能如下:
```json
"tag": {
"href": "http://example.org/users/someone",
"name": "@someone@example.org",
"type": "Mention"
}
```
如果用户提及同一实例内的本站用户,他们的完整 `name` 仍会被包含。
例如,一个 GoToSocial 用户在域 `some.gotosocial.instance` 上提及同一实例内的用户 `user2`。他们还提及了 `@someone@example.org`。外发 Note 的 `tag` 属性如下:
```json
"tag": [
{
"href": "http://example.org/users/someone",
"name": "@someone@example.org",
"type": "Mention"
},
{
"href": "http://some.gotosocial.instance/users/user2",
"name": "@user2@some.gotosocial.instance",
"type": "Mention"
}
]
```
为了方便外站GoToSocial 始终在外发的提及中提供 `href``name` 属性。GoToSocial 使用的 `href` 属性始终是目标账户的 ActivityPub ID/URI而不是网页 URL。
### 传入
GoToSocial 尝试以与发送出相同的方式解析传入提及:作为 `tag` 属性中的 `Mention` 类型条目。然而,解析传入提及时,对于必须设置哪些属性的要求会更宽松些。
GoToSocial 更偏好 `href` 属性,它可以是目标的 ActivityPub ID/URI 或网页 URL如果 `href` 不存在,将使用 `name` 属性作为回退。如果两个属性都不存在,提及将被视为无效并被丢弃。
## 内容、内容映射和语言
与其他 ActivityPub 实现一致GoToSocial 在 `Objects` 中使用 `content``contentMap` 字段来推断传入贴文的内容和语言,并在外发贴文中设置内容和语言。
### 外发
如果一个外发 `Object`(通常是 `Note`)有内容,它将以在 `content` 字段中以被字符串化的 HTML 形式给出。
如果 `content` 关联特定用户选择的语言,那么 `Object` 还将设置 `contentMap` 属性为单条目键/值映射,其中键是 BCP47 语言话题标签,值是与 `content` 字段相同的内容。
例如,一篇用英语 (`en`) 写的贴文将如下所示:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"attributedTo": "http://example.org/users/i_p_freely",
"to": "https://www.w3.org/ns/activitystreams#Public",
"cc": "http://example.org/users/i_p_freely/followers",
"id": "http://example.org/users/i_p_freely/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
"url": "http://example.org/@i_p_freely/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
"published": "2021-11-20T13:32:16Z",
"content": "<p>This is an example note.</p>",
"contentMap": {
"en": "<p>This is an example note.</p>"
},
"attachment": [],
"replies": {...},
"sensitive": false,
"summary": "",
"tag": {...}
}
```
如果贴文有内容GoToSocial 会始终设置 `content` 字段,但是如果使用的 GoToSocial 版本较旧、用户未设置语言,或设置的语言不是公认的 BCP47 语言标签,则可能不会始终设置 `contentMap` 字段。
### 传入
GoToSocial 在解析传入的 `Object` 时使用 `content``contentMap` 属性来确定内容并推断该内容的主要语言。它使用以下算法:
#### 仅设置 `content`
仅采用该内容并将语言标记为未知。
#### 同时设置 `content``contentMap`
`contentMap` 中查找键,作为语言标签,要查找的键的值与 `content` 中的 HTML 字符串匹配。
如果找到匹配项,将其用作贴文的语言。
如果未找到匹配项,则保留 `content` 中的内容并将语言标记为未知。
#### 仅设置 `contentMap`
如果 `contentMap` 只有一个条目,则将语言标签和内容(值)作为“主要”语言和内容。
如果 `contentMap` 有多个条目,则无法确定贴文的意图内容和语言,因为映射顺序不可预测。在这种情况下,尝试从 GoToSocial 实例的[配置语言](../configuration/instance.md)中选择与其中一种语言匹配的语言和内容条目。如果无法通过这种方式匹配语言,则从 `contentMap` 中随机选择一个语言和内容条目作为“主要”语言和内容。
!!! Note
在上述所有情况下,如果推断的语言无法解析为有效的 BCP47 语言话题标签,则语言将回退为未知。
## 互动规则
GoToSocial 使用 `interactionPolicy` 属性告知外站给定帖文允许的互动类型(有前提)。
!!! danger
互动规则旨在限制用户贴文上用户不希望的回复和其他互动的有害影响(例如,“回复家(reply guys)” —— 不请自来地发表冒失回复的人)。
然而,这远远不能满足这一目的,因为仍然有许多“额外”方式可以分发或回复贴文,进而超出用户的初衷或意图。
例如,用户可能创建一个附有非常严格互动规则的贴文,却发现其他服务器软件不尊重该规则,而其他实例上的用户从他们的实例视角进行讨论并回复该贴文。原始发布者的实例将自动从视图中删除这些用户不想要的互动,但外站实例可能仍会显示它们。
另一个例子:有人可能会看到一个指定没人可以回复的贴文,但截屏该贴文,将截屏作为新帖文发布,并将提及原作者或只是附上原贴文的 URL。在这种情况下他们成功通过新创建的贴文串来达到“回复”该贴文的目的。
无论好坏GoToSocial 只能为这一部分问题提供尽最大努力的部分技术解决方案,这更多的是一个社会行为和边界的问题。
### 概述
`interactionPolicy` 是贴文类 `Object`(如 `Note``Article``Question` 等)附带的一个属性,其格式如下:
```json
{
[...],
"interactionPolicy": {
"canLike": {
"always": [ "始终可进行此操作的零个或多个 URI" ],
"approvalRequired": [ "需要批准才能进行此操作的零个或多个 URI" ]
},
"canReply": {
"always": [ "始终可进行此操作的零个或多个 URI" ],
"approvalRequired": [ "需要批准才能进行此操作的零个或多个 URI" ]
},
"canAnnounce": {
"always": [ "始终可进行此操作的零个或多个 URI" ],
"approvalRequired": [ "需要批准才能进行此操作的零个或多个 URI" ]
}
},
[...]
}
```
在此对象中:
- `canLike` 指定可创建 `Like` 并将帖文 URI 作为 `Like``Object` 的人。
- `canReply` 指定可创建 `inReplyTo` 设置为帖文 URI 的帖文的人。
- `canAnnounce` 指定可创建 `Announce` 并将帖文 URI 作为 `Announce``Object` 的人。
并且:
- `always` 是一个 ActivityPub URI/ID 的 `Actor``Actor``Collection`,无需 `Accept` 即可进行互动分发到其粉丝。
- `approvalRequired` 是一个 ActivityPub URI/ID 的 `Actor``Actor``Collection`,可以互动,但在将互动分发给其粉丝之前需要获得 `Accept`
`always``approvalRequired` 的有效 URI 条目包括 magic ActivityStreams 公共 URI `https://www.w3.org/ns/activitystreams#Public`,贴文创建者的 `Following` 和/或 `Followers` 集合的 URI以及个人请求体的 URI。例如
```json
[
"https://www.w3.org/ns/activitystreams#Public",
"https://example.org/users/someone/followers",
"https://example.org/users/someone/following",
"https://example.org/users/someone_else",
"https://somewhere.else.example.org/users/someone_on_a_different_instance"
]
```
### 指定无人能进行的操作
!!! note
即使规则指定无人可互动GoToSocial 仍做出默认假设。参见[默认假设](#默认假设)。
空数组或缺少/空的键表示无人能进行此互动。
例如,以下 `canLike` 指定无人能 `Like` 该贴文:
```json
"canLike": {
"always": [],
"approvalRequired": []
},
```
类似的,`canLike` 值为 `null` 也表示无人能 `Like` 该帖:
```json
"canLike": null
```
```json
"canLike": {
"always": null,
"approvalRequired": null
}
```
缺失的 `canLike` 值同样表达了相同的意思:
```json
{
[...],
"interactionPolicy": {
"canReply": {
"always": [ "始终可进行此操作的零个或多个 URI" ],
"approvalRequired": [ "需要批准才能进行此操作的零个或多个 URI" ]
},
"canAnnounce": {
"always": [ "始终可进行此操作的零个或多个 URI" ],
"approvalRequired": [ "需要批准才能进行此操作的零个或多个 URI" ]
}
},
[...]
}
```
### 冲突/重复值
在用户位于集合 URI 中, 且也通过 URI 被显式指定的情况下,**更具体的值**优先。
例如:
```json
[...],
"canReply": {
"always": [
"https://example.org/users/someone"
],
"approvalRequired": [
"https://www.w3.org/ns/activitystreams#Public"
]
},
[...]
```
在此情形下,`@someone@example.org` 位于 `always` 数组中,并且也存在于 `approvalRequired` 数组中的 magic ActivityStreams 公共集合中。在这种情况下,他们可以始终回复,因为 `always` 值更为明确。
另一个例子:
```json
[...],
"canReply": {
"always": [
"https://www.w3.org/ns/activitystreams#Public"
],
"approvalRequired": [
"https://example.org/users/someone"
]
},
[...]
```
在此,`@someone@example.org` 位于 `approvalRequired` 数组中,但也隐含地存在于 `always` 数组中的 magic ActivityStreams 公共集合中。在这种情况下,除了 `@someone@example.org` 需要批准外,其他人都可以无需批准进行回复。
在相同 URI 存在于 `always``approvalRequired` 两者中时,**最高级别的权限**优先(即 `always` 中的 URI 优先于 `approvalRequired` 中的相同 URI
### 默认假设
GoToSocial 对 `interactionPolicy` 做出若干默认假设。
**首先**,无论贴文的可见性和 `interactionPolicy` 如何,被[提及](#提及)或回复的用户应**始终**能够回复该贴而无需批准,**除非**提及或回复他们的贴文本身正在等待批准。
这是为了防止潜在的骚扰者在辱骂贴文中提及某人,并不给被提及的用户任何回复的机会。
因此当发送互动规则时GoToSocial 始终将提及用户的 URI 添加到 `canReply.always` 数组中,除非它们已被 magic ActivityStreams 公共 URI 覆盖。
同样,在执行接收到的互动规则时,即使用户 URI 不存在于 `canReply.always` 数组中GoToSocial 也将被提及用户的 URI 视作已存在。
**其次**,用户应**始终**能够回复自己的贴文,点赞自己的贴文,并转发自己的贴文而无需批准,**除非**该贴文本身正在等待批准。
因此当发送互动规则时GoToSocial 始终将贴文作者的 URI 添加到 `canLike.always``canReply.always``canAnnounce.always` 数组中,除非它们已被 magic ActivityStreams 公共 URI 覆盖。
同样,在执行接收到的互动规则时,即使贴文作者 URI 不存在于这些 `always` 数组中GoToSocial 也始终将贴文作者 URI 视为已存在。
### 默认值
当贴文上没有 `interactionPolicy` 属性时GoToSocial 会根据贴文可见级别和发帖作者为该帖假定默认的 `interactionPolicy`
对于 `@someone@example.org` 的**公开**或**未列出**贴文,默认 `interactionPolicy` 为:
```json
{
[...],
"interactionPolicy": {
"canLike": {
"always": [
"https://www.w3.org/ns/activitystreams#Public"
],
"approvalRequired": []
},
"canReply": {
"always": [
"https://www.w3.org/ns/activitystreams#Public"
],
"approvalRequired": []
},
"canAnnounce": {
"always": [
"https://www.w3.org/ns/activitystreams#Public"
],
"approvalRequired": []
}
},
[...]
}
```
对于 `@someone@example.org` 的**仅限粉丝**贴文,假定的 `interactionPolicy` 为:
```json
{
[...],
"interactionPolicy": {
"canLike": {
"always": [
"https://example.org/users/someone",
"https://example.org/users/someone/followers",
[...提及的用户的 URI...]
],
"approvalRequired": []
},
"canReply": {
"always": [
"https://example.org/users/someone",
"https://example.org/users/someone/followers",
[...提及的用户的 URI...]
],
"approvalRequired": []
},
"canAnnounce": {
"always": [
"https://example.org/users/someone"
],
"approvalRequired": []
}
},
[...]
}
```
对于 `@someone@example.org` 的**私信**贴文,假定的 `interactionPolicy` 为:
```json
{
[...],
"interactionPolicy": {
"canLike": {
"always": [
"https://example.org/users/someone",
[...提及的用户的 URI...]
],
"approvalRequired": []
},
"canReply": {
"always": [
"https://example.org/users/someone",
[...提及的用户的 URI...]
],
"approvalRequired": []
},
"canAnnounce": {
"always": [
"https://example.org/users/someone"
],
"approvalRequired": []
}
},
[...]
}
```
### 示例 1 - 限制对话范围
在此示例中,用户 `@the_mighty_zork` 想开始与用户 `@booblover6969``@hodor` 之间的对话。
为了避免讨论被他人打断,他们希望来自三名参与者以外的用户的回复仅在获得 `@the_mighty_zork` 批准后才被允许。
此外,他们希望将贴文转发/`Announce` 的权利限制为仅限于他们自己的粉丝和三个对话参与者。
然而,任何人都可以 `Like` `@the_mighty_zork` 的贴文。
这可以通过以下 `interactionPolicy` 来实现,它附加在可见性为公开的帖文上:
```json
{
[...],
"interactionPolicy": {
"canLike": {
"always": [
"https://www.w3.org/ns/activitystreams#Public"
],
"approvalRequired": []
},
"canReply": {
"always": [
"https://example.org/users/the_mighty_zork",
"https://example.org/users/booblover6969",
"https://example.org/users/hodor"
],
"approvalRequired": [
"https://www.w3.org/ns/activitystreams#Public"
]
},
"canAnnounce": {
"always": [
"https://example.org/users/the_mighty_zork",
"https://example.org/users/the_mighty_zork/followers",
"https://example.org/users/booblover6969",
"https://example.org/users/hodor"
],
"approvalRequired": []
}
},
[...]
}
```
### 示例 2 - 长独白贴文串
在此示例中,用户 `@the_mighty_zork` 想写一个长篇独白。
他们不介意别人转发和点赞贴文,但不想收到任何回复,因为他们没有精力去管理讨论;他们只是想通过发泄自己的想法去表达自我。
这可以通过在贴文串中的每个贴文上设置以下 `interactionPolicy` 来实现:
```json
{
[...],
"interactionPolicy": {
"canLike": {
"always": [
"https://www.w3.org/ns/activitystreams#Public"
],
"approvalRequired": []
},
"canReply": {
"always": [
"https://example.org/users/the_mighty_zork"
],
"approvalRequired": []
},
"canAnnounce": {
"always": [
"https://www.w3.org/ns/activitystreams#Public"
],
"approvalRequired": []
}
},
[...]
}
```
在这里,任何人都可以点赞或转发,但无人能够回复(除了 `@the_mighty_zork` 自己)。
### 示例 3 - 完全开放
在此示例中,`@the_mighty_zork` 想写一篇完全开放的贴文,任何能看到此帖的人都可以进行回复、转发或点赞(即解锁和公开贴文默认行为):
```json
{
[...],
"interactionPolicy": {
"canLike": {
"always": [
"https://www.w3.org/ns/activitystreams#Public"
],
"approvalRequired": []
},
"canReply": {
"always": [
"https://www.w3.org/ns/activitystreams#Public"
],
"approvalRequired": []
},
"canAnnounce": {
"always": [
"https://www.w3.org/ns/activitystreams#Public"
],
"approvalRequired": []
}
},
[...]
}
```
### 请求、获得和验证批准
当用户的 URI 在需要获得批准的互动的 `approvalRequired` 数组中时,如果他们希望获得批准以分发互动,应该执行以下步骤:
1. 像往常一样撰写互动 `Activity`(即 `Like``Create` (回复)或 `Announce`)。
2. 像往常一样将 `Activity``to``cc` 地址设为预期的收件人。
3. 将 `Activity` **仅**发送到要互动帖的作者的 `Inbox`(或 `sharedInbox`)。
4. **此时不要进一步分发 `Activity`**
此时,互动可视为等待批准,并不应该显示在被互动的贴文的回复或点赞集合等中。
可以向发送互动的用户显示“互动待批准”状态,但理想情况下不应该向与该用户共享实例的其他用户显示。
从这里开始,可能会出现以下三种情况之一:
#### 拒绝
在这种情况下,互动目标贴文的作者发回一个 `Reject` `Activity`,该活动的 `Object` 属性带有待拒绝互动活动的 URI/ID。
例如,以下 JSON 对象 `Reject``@someone@somewhere.else.example.org` 回复 `@post_author@example.org` 贴文的尝试:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "https://example.org/users/post_author",
"to": "https://somewhere.else.example.org/users/someone",
"id": "https://example.org/users/post_author/activities/reject/01J0K2YXP9QCT5BE1JWQSAM3B6",
"object": "https://somewhere.else.example.org/users/someone/statuses/01J17XY2VXGMNNPH1XR7BG2524",
"type": "Reject"
}
```
如果发生这种情况,`@someone@somewhere.else.example.org`(及其实例)应视交互为被拒绝。该实例应从其内部存储(即数据库)中删除该活动,或以其他方式表明它已被拒绝,并且不应进一步分发该 `Activity` 或重试该交互。
#### 无响应
在这种情况下,正在互动的贴文的作者从不发送 `Reject``Accept` `Activity`。在这种情况下,交互被视为永久“待处理”。实例可能希望实现某种清理功能,达到一定时间期限的已发送且待处理交互应被视为过期,然后按照上述方式被处理为 `Rejected` 并删除。
#### 接受
在这种情况下,正在互动的贴文的作者发回一个`Accept` `Activity`,该活动的 `Object` 属性带有待批准互动活动的 URI/ID。
例如,以下 JSON 对象 `Accept``@someone@somewhere.else.example.org` 回复 `@post_author@example.org` 贴文的尝试:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "https://example.org/users/post_author",
"cc": [
"https://www.w3.org/ns/activitystreams#Public",
"https://example.org/users/post_author/followers"
],
"to": "https://somewhere.else.example.org/users/someone",
"id": "https://example.org/users/post_author/activities/accept/01J0K2YXP9QCT5BE1JWQSAM3B6",
"object": "https://somewhere.else.example.org/users/someone/statuses/01J17XY2VXGMNNPH1XR7BG2524",
"type": "Accept"
}
```
如果发生这种情况,`@someone@somewhere.else.example.org`(及其实例)应视为交互已被批准/接受。然后,该实例可以自由地将此交互 `Activity` 分发给所有由 `to``cc` 等目标的接收者,并附加属性 `approvedBy`
### 验证在粉丝/关注中是否存在
如果一个 `Actor` 在其互动规则的 `always` 字段中因为存在于 `Followers``Following` 集合中而被允许进行交互(例如 `Like``inReplyTo``Announce`),则其服务器仍应等待来自目标帐户服务器的 `Accept`,然后才更广泛地分发交互,并将 `approvedBy` 属性设置为 `Accept` 的 URI。
这样可以防止第三方服务器被迫以某种方式验证互动的 `Actor` 是否存在于接收互动的用户的 `Followers``Following` 集合中。让目标服务器进行验证,并采信其 `Accept` ,将其视为交互 `Actor` 存在于相关集合中的证明,更为简单。
同样,当接收到一个具有匹配 `Following``Followers` 集合的 `Actor` 的互动请求时,接收互动的 `Actor` 的服务器应确保尽快发送出 `Accept`,以便交互 `Actor` 服务器可以带着适当的接受证明发送出 `Activity`
这个过程应绕过通常的“待批准”阶段,因此没有必要通知 `Actor` 待批准的交互,因为他们已明确同意。在 GoToSocial 代码库中,这被称为“预批准”。
### `approvedBy`
`approvedBy` 是一个附加属性,添加到 `Like``Announce` 活动以及任何被视为“贴文”的 `Object`(如 `Note``Article`)中。
`approvedBy` 的存在表明贴文的作者接受了由 `Activity` 作为目标或由 `Object` 所回复的互动,并现在可以分发给其预期观众。
`approvedBy` 的值应为创建 `Accept` `Activity` 的接收交互贴文作者的 URI。
例如,以下 `Announce` `Activity``approvedBy` 表示它已被 `@post_author@example.org` `Accept`
```json
{
"actor": "https://somewhere.else.example.org/users/someone",
"to": [
"https://somewhere.else.example.org/users/someone/followers"
],
"cc": [
"https://example.org/users/post_author"
],
"id": "https://somewhere.else.example.org/users/someone/activities/announce/01J0K2YXP9QCT5BE1JWQSAM3B6",
"object": "https://example.org/users/post_author/statuses/01J17ZZFK6W82K9MJ9SYQ33Y3D",
"approvedBy": "https://example.org/users/post_author/activities/accept/01J18043HGECBDZQPT09CP6F2X",
"type": "Announce"
}
```
接收到一个带有 `approvedBy` 值的 `Activity` 时,外站实例应解引用字段的 URI 值以获取 `Accept` `Activity`
然后,他们应验证 `Accept` `Activity``object` 值是否等于交互 `Activity``Object``id`,并验证 `actor` 值是否等于接收交互的贴文的作者。
此外,他们应确保解引用的 `Accept` 的 URL 域名等于接收交互贴文的作者的 URL 域名。
如果无法解引用 `Accept` 或未通过有效性检查,则交互应被视为无效并丢弃。
由于这种验证机制,实例应确保他们对涉及 `interactionPolicy``Accept` URI 的解引用响应提供一个有效的 ActivityPub 对象。如果不这样做,他们会无意中限制外站实例分发其贴文的能力。
### 后续回复/范围扩展
对话中的每个后续回复将有其自己的互动规则,由创建回复的用户选择。换句话说,整个*对话*或*贴文串*并不由一个 `interactionPolicy` 控制,而是贴文串中的每个后续贴文可以由贴文作者设置不同的规则。
不幸的是,这意味着即使有 `interactionPolicy`,贴文串的范围也可能不小心超出第一个贴文作者的意图。
例如,在上述[示例 1](#示例-1---限制对话范围)中,`@the_mighty_zork` 在第一个贴文中指定了 `canReply.always` 值为
```json
[
"https://example.org/users/the_mighty_zork",
"https://example.org/users/booblover6969",
"https://example.org/users/hodor"
]
```
在后续回复中,`@booblover6969` 无意或有意地将 `canReply.always` 值设为:
```json
[
"https://www.w3.org/ns/activitystreams#Public"
]
```
这扩大了对话的范围,因为现在任何人都可以回复 `@booblover6969` 的贴文,并可能也在该回复中标记 `@the_mighty_zork`
为了避免这个问题,建议外站实例防止用户能够扩大范围(具体机制待定)。
同时,实例应将任何与仍处于待批准状态的贴文或贴文类似的 `Object` 的交互视作待批准。
换句话说,只要某条贴文处于待批准状态,实例应将该贴文下的所有互动标记为待批准,无论此贴文的互动规则通常允许什么。
这可避免有用户回复贴文,且在回复尚未得到批准的情况下继续回复*他们自己的回复*并将其标记为允许(作为贴文回复的作者,他们默认拥有对贴文回复的[回复权限](#默认假设))。
## 投票
为了联合投票状态GoToSocial 使用广泛采用的 [ActivityStreams `Question` 类型](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-question)。然而,第一个由 Mastodon 引入和推广的这个类型略微偏离 ActivityStreams 规范。在规范中Question 类型被标记为 `IntransitiveActivity` 的扩展,此扩展是一个应当不带 `Object` 且所有详细信息应被默认包含的 `Activity` 扩展。但在具体实现中,它作为 `Object` 通过 `Create``Update` 活动传递。
值得注意的是,虽然 GoToSocial 内部可能将投票视作贴文附件的一种类型,但 ActivityStreams 表示法将贴文和带投票的贴文视为两种不同的 `Object` 类型。贴文以 `Note` 类型联合,投票以 `Question` 类型联合。
GoToSocial 传输(和期望接收)的 `Question` 类型包含所有常见的 `Note` 属性外加一些附加内容。它们期望以下附加的JSON
```json
{
"@context":[
{
// toot:votersCount 扩展,用于添加 votersCount 属性。
"toot":"http://joinmastodon.org/ns#",
"votersCount":"toot:votersCount"
}
],
// oneOf / anyOf 包含投票选项
// 本身。只有其中之一会被设置,
// 其中 "oneOf" 表示单选投票,
// "anyOf" 表示多选投票。
//
// 任一属性都包含一个 “Notes” 数组,
// 特殊的是它们包含一个 “name” 且未设置
// “content”其中 “name” 代表实际
// 投票选项字符串。此外,它们包含
// 一个 “Collection” 类型的 “replies” 属性,
// 通过 “totalItems” 表示每个投票选项当前已知的投票数。
"oneOf": [ // 或 "anyOf"
{
"type": "Note",
"name": "选项 1",
"replies": {
"type": "Collection",
"totalItems": 0
}
},
{
"type": "Note",
"name": "选项 2",
"replies": {
"type": "Collection",
"totalItems": 0
}
}
],
// endTime 指示此投票将何时结束。
// 某些服务器实现支持永不结束的投票,
// 或使用 “closed” 来暗示 “endTime”因此该项可能不会总是被设置。
"endTime": "2023-01-01T20:04:45Z",
// closed 指示此投票结束的时间。
// 在来到此时间之前,该项将不会被设置。
"closed": "2023-01-01T20:04:45Z",
// votersCount 表示参与者的总数,
// 这在多选投票的情况下很有用。
"votersCount": 10
}
```
### 外发
你可以期望从 GoToSocial 接收到一个 `Question` 形式的投票,投票在 `Create``Update` 活动中作为对象属性传递。在 `Update` 活动的情形下,如果投票中除了 `votersCount``replies.totalItems``closed` 之外的任何内容发生了变化,那么就表明包裹的贴文以需要重新创建的方式进行了编辑,因此需要重置。你可以期望在以下时间收到这些活动:
- "Create":刚刚创建了带有附加投票的贴文
- "Update":投票/投票人数发生了变化,或者投票刚刚结束
你可以期望的,由 GoToSocial 生成的 `Question` 可以在上面的伪 JSON 中看到。在此 JSON 中,"endTime" 字段将始终被设置(因为我们不支持创建无尽投票),而 "closed" 字段只有在投票结束时才会设置。
### 传入
GoToSocial 期望以与发出投票几乎相同的方式接收投票,在解析 `Question` 对象时采用略显宽容的规则。
- 如果提供 "closed" 而不提供 "endTime",那么这也将被视为 "endTime" 的值
- 如果既没有提供 "closed" 也没有 "endTime",则认为投票是永不结束的投票
- 任何情况下,若一个带有 `Question``Update` 活动提供了一个 `closed` 时间,而之前的活动没有提供,则假定投票刚刚关闭。这将在本站参与投票的用户的客户端触发通知
## 投票行动
为了联合投状态票GoToSocial 使用特殊格式化 [ActivityStreams "Note" 类型](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note)。这被 ActivityPub 服务器广泛接受为联合投票的方式,仅将投票作为 "Create" 活动的 "Object" 附加对象。
GoToSocial 传输的 "Note" 类型(以及期望接收到的)包含内容有:
- "name": [确切的投票选项文本]
- "content": [未设置]
- "inReplyTo": [指向 AS Question 的 IRI]
- "attributedTo": [投票作者的 IRI]
- "to": [投票作者的 IRI]
例如:
```json
{
"type": "Note",
"name": "选项 1",
"inReplyTo": "https://example.org/users/bobby_tables/statuses/123456",
"attributedTo": "https://sample.com/users/willy_nilly",
"to": "https://example.org/users/bobby_tables"
}
```
### 外发
你可以期望以上面特定描述形式接收到来自 GoToSocial 的投票。投票仅作为附属于 "Create" 活动的对象发送。
特别地如上节所述GoToSocial 会在 `name` 字段中提供选项文本,不设置 `content` 字段,在 `inReplyTo` 字段提供一个 IRI指向你的实例上的带投票贴文。
以下是一个 `Create` 示例,其中用户 `https://sample.com/users/willy_nilly` 在用户 `https://example.org/users/bobby_tables` 创建的多选投票中投票:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "https://sample.com/users/willy_nilly",
"id": "https://sample.com/users/willy_nilly/activity#vote/https://example.org/users/bobby_tables/statuses/123456",
"object": [
{
"attributedTo": "https://sample.com/users/willy_nilly",
"id": "https://sample.com/users/willy_nilly#01HEN2R65468ZG657C4ZPHJ4EX/votes/1",
"inReplyTo": "https://example.org/users/bobby_tables/statuses/123456",
"name": "纸巾",
"to": "https://example.org/users/bobby_tables",
"type": "Note"
},
{
"attributedTo": "https://sample.com/users/willy_nilly",
"id": "https://sample.com/users/willy_nilly#01HEN2R65468ZG657C4ZPHJ4EX/votes/2",
"inReplyTo": "https://example.org/users/bobby_tables/statuses/123456",
"name": "金融时报",
"to": "https://example.org/users/bobby_tables",
"type": "Note"
}
],
"published": "2021-09-11T11:45:37+02:00",
"to": "https://example.org/users/bobby_tables",
"type": "Create"
}
```
### 传入
GoToSocial 期望以与发送投票的几乎相同形式接收投票。即只会期望把投票作为 "Create" 活动的一部分接收。
特别地GoToSocial 将 votes 识别为不同于其他 "Note" 对象,因为其包含一个 "name" 字段,省略 "content" 字段,且 "inReplyTo" 字段是指向带附有投票的贴文的 URI。 如果满足这些条件GoToSocial 将把提供的 "Note" 视为格式不正确的贴文对象。
## 贴文删除
GoToSocial 允许用户删除他们创建的贴文。这些删除操作将会向其他实例进行联合,其他实例也应删除其缓存的贴文。
### 外发
当 GoToSocial 用户删除贴文时,服务器会向其他实例发送一个 `Delete` 活动。
`Delete` 活动的 `Object` 条目会设置为该贴文的 ActivityPub URI。
`to``cc` 将根据原始贴文的可见性以及任何被提及/回复的用户进行设置。
如果原始贴文不是私信ActivityPub `Public` URI 将在 `to` 中注明。否则,只会涉及被提及和回复的用户。
在以下示例中,'admin' 用户删除了一篇公开贴文,其中提到了 'foss_satan' 用户:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "http://example.org/users/admin",
"cc": [
"http://example.org/users/admin/followers",
"http://fossbros-anonymous.io/users/foss_satan"
],
"object": "http://example.org/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
"to": "https://www.w3.org/ns/activitystreams#Public",
"type": "Delete"
}
```
在下一个示例中,'1happyturtle' 用户删除了一条原本发给 'the_mighty_zork' 用户的直接消息。
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "http://example.org/users/1happyturtle",
"cc": [],
"object": "http://example.org/users/1happyturtle/statuses/01FN3VJGFH10KR7S2PB0GFJZYG",
"to": "http://somewhere.com/users/the_mighty_zork",
"type": "Delete"
}
```
要处理从 GoToSocial 实例发出的 `Delete` 活动,外站实例应检查其是否根据提供的 URI 存储了 `Object`。如果有,它们应从本站缓存中删除该对象。如果没有,那么无需执行任何操作,因为它们从未存储过现在被删除的贴文。
### 接收
GoToSocial 尽可能彻底地处理来自外站实例的 `Delete` 活动,以尊重其他用户的隐私。
当 GoToSocial 实例收到 `Delete` 时,它会尝试从 `Object` 字段中提取被删除的贴文 URI。如果 `Object` 仅是一个 URI则使用该 URI。如果 `Object` 是一个 `Note` 或其他常用于表示贴文的类型,则会从中提取 URI。
然后GoToSocial 将检查其是否存储了具有给定 URI 的贴文。如果有,它将在数据库和所有用户时间线上完全删除。
GoToSocial 仅在确认原帖是被 `Delete` 所属的 `actor` 所拥有的情况下才会删除对应贴文。
## 贴文串
由于去中心化和联合的特性Fediverse 上的任何一个服务器几乎不可能知道给定贴文串中的每篇贴文。
即便如此,也可以尽力对贴文串进行解引用,从不同的外站实例拉取回复,以更充分地展现整个对话。
GoToSocial 通过在对话贴文串上下迭代,尽可能获取外站贴文,来实现这一点。
假设我们有两个账户:`local_account``our.server` 上,`remote_1``remote.1` 上。
在这种情况下,`local_account` 关注了 `remote_1`,所以 `remote_1` 的贴文会出现在 `local_account` 的主页时间线上。
现在,`remote_1` 转发/转贴了来自第三方账户 `remote_2` 的一篇贴文,该账户在服务器 `remote.2` 上。
`local_account` 未关注 `remote_2``our.server` 上也没有其他人关注,因此 `our.server` 未曾见过 `remote_2` 的这篇贴文。
![贴文串的示意图,展示了来自 remote_2 的贴文,以及可能的祖先和后代贴文](../public/diagrams/conversation_thread.png)
此时GoToSocial 会对 `remote_2` 的贴文进行“解引用”,检查其是否属于某个贴文串,以及贴文串的其他部分是否可以获取。
GtS 首先检查贴文的 `inReplyTo` 属性,该属性在贴文回复其他贴文时设置。[见此处](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-inreplyto)。如果设置了 `inReplyTo`GoToSocial 会解引用被回复的贴文。如果 *这篇* 贴文也设置了 `inReplyTo`,那么 GoToSocial 也会对此进行解引用,如此反复。
一旦获取到贴文的所有 **祖先**GtS 将开始处理贴文的 **后代**
这种情况下通过检查解引用贴文的 `replies` 属性,依次处理回复及回复的回复。[见此处](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-replies)。
这个贴文串解引用的过程可能需要进行多次 HTTP 请求到不同的服务器,尤其是在贴文串长且复杂的情况下。
解引用的最终结果是,假设由 `remote_2` 转发的贴文属于一个贴文串,那么 `local_account` 在主页时间线上打开贴文时,现在应该能够看到贴文串中的贴文。换句话说,他们将看到来自其他服务器账户的回复(他们可能尚未相遇),以及由 `remote_2` 发布的贴文串之前和之后的贴文。
这为 `local_account` 提供更完整的对话视图,而不仅仅是孤立和断章取义地看到被转发的贴文。此外,这还为 `local_account` 提供了根据对 `remote_2` 的回复发现新账户以进行关注的机会。

View file

@ -0,0 +1,7 @@
# 请求限流与速率限制
GoToSocial 对 ActivityPub API 端点(收件箱、用户端点、表情符号等)应用了 HTTP 请求限流和速率限制。
这可确保外站服务器不能用虚假请求淹没 GoToSocial 实例。外站服务器在对 ActivityPub API 端点进行 GET 或 POST 请求时,应尊重 429 和 503 HTTP 状态码,并考虑 `retry-after` HTTP 响应头。
有关请求限流和速率限制行为的更多详细信息,请参阅 [限流](../api/throttling.md) 和 [速率限制](../api/ratelimiting.md) 文档。