跳至主要内容

ActivityPub 和 Fediverse 协议

本页面文档记录了 Owncast 中的 ActivityPub 实现,涵盖以下协议级内容:服务器 发送 的活动、接收 的活动、如何识别自身以及如何签名和验证请求。 此文档旨在帮助希望构建与 Owncast 互操作的 Fediverse 应用程序的开发者,不论是指从其他平台关注 Owncast 服务器、实时接收其通知,还是构建懂得 Owncast 自定义扩展的工具。

如果您是 Owncast 操作员并且只想开启联邦功能,请查看 Fediverse启用社交功能。 本页面假设您已熟悉 ActivityPub、ActivityStreams 2.0、JSON-LD 和 HTTP 签名。

心理模型

Owncast 服务器作为类型为 Service单个参与者。 每个服务器有一个帐户(默认用户名为 live),它代表的是流本身,而不是一个人。 与通用社交服务器相比,该模型是故意狭窄的:

  • 该参与者 发送 帖子给其关注者(最重要的是 “直播” 通知)和定期的流 “ping”。
  • The actor receives follows, likes, boosts (announces), replies and mentions, quote requests, and a handful of server-to-server activities. Inbound posts are only surfaced to the operator and plugins: they are never added to a timeline and never re-federated.
  • 正好有一个用户,无开放注册,following 集合总是为空。

当联邦被禁用时,所有联邦端点返回 405 Method Not Allowed,因此如果服务器无法访问,请先检查此项。

发现

远程应用程序通过标准的知名发现机制定位和描述 Owncast 参与者。

WebFinger

GET /.well-known/webfinger?resource=acct:{username}@{host}

resource 必须是 acct: URI,其主机与服务器配置的主机匹配(否则请求将被拒绝,返回 501/400)。 响应作为 application/jrd+json 提供:

{
"subject": "acct:live@owncast.example.com",
"aliases": ["https://owncast.example.com/federation/user/live"],
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": "https://owncast.example.com/federation/user/live"
},
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://owncast.example.com/federation/user/live"
},
{
"rel": "http://webfinger.net/rel/avatar",
"type": "image/png",
"href": "https://owncast.example.com/logo/external"
},
{
"rel": "alternate",
"type": "application/x-mpegURL",
"href": "https://owncast.example.com/hls/stream.m3u8"
}
]
}

self 链接是规范的参与者 IRI。 请注意 Owncast 特定的 alternate 类型为 application/x-mpegURL 的链接:它直接指向流的 HLS 播放列表,使客户端能在无需抓取网页 UI 的情况下发现直播视频。

host-meta

GET /.well-known/host-meta

返回一个 XRD 文档,指向 WebFinger 端点,以便从 host-meta 启动的客户端使用:

<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" type="application/json"
template="https://owncast.example.com/.well-known/webfinger?resource={uri}"/>
</XRD>

NodeInfo

Owncast 通过 NodeInfo 暴露服务器级元数据,以便 Fediverse 爬虫、目录和统计网站可以描述该实例。

NodeInfo 发现GET /.well-known/nodeinfo

{
"links": [
{
"rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
"href": "https://owncast.example.com/nodeinfo/2.0"
}
]
}

NodeInfo 2.0GET /nodeinfo/2.0

{
"version": "2.0",
"software": {
"name": "owncast",
"version": "0.2.x"
},
"protocols": ["activitypub"],
"services": {
"inbound": [],
"outbound": []
},
"usage": {
"users": {
"total": 1,
"activeMonth": 1,
"activeHalfyear": 1
},
"localPosts": 42
},
"openRegistrations": false,
"metadata": {
"chat_enabled": true,
"federation": {
"username": "live",
"featured_streams": 1
}
}
}

其中大部分是标准的 NodeInfo,包含的一些 Owncast 特定信号值得指出:

  • software.name 始终为 owncast。 这是检测您是否与 Owncast 服务器通信的最可靠方式。
  • usage.users.total 始终为 1,并且 openRegistrations 始终为 false — Owncast 实例是一个单参与者服务器,而不是多用户社区。
  • usage.localPosts 是服务器发送的活动数量(直播通知和其他公共消息),它是流活跃程度的一个有用代理。
  • metadata.chat_enabled 反映了 Owncast 的内置聊天是否启用。
  • metadata.federation 是 Owncast 特定的块:
    • username 是参与者的首选用户名(默认 live)。 与主机结合,这让您无需单独的 WebFinger 往返请求即可获取 acct: 句柄。
    • featured_streams 指示参与了特色流/小型目录的流转(请参见下面的 流 ping)。 值为 1 意味着服务器通过定期的 Offer 活动向关注者宣传其直播状态。

x-nodeinfo2GET /.well-known/x-nodeinfo2 提供相同的信息,采用某些目录使用的替代 x-nodeinfo2 形式,包含一个 organization 块(namecontact)和一个 activeWeek 用户数字。 在这里,services.inbound/services.outbound 都是 ["activitypub"]

Mastodon 实例 APIGET /api/v1/instance 返回一个 Mastodon 兼容的实例描述(urititleshort_descriptiondescriptionversionthumbnailstats 和注册标志),以便 Mastodon 知情工具能够渲染一个熟悉的实例卡。 stats.user_count1stats.status_count 是本地帖子数量,注册/批准/邀请均已禁用。

该行为者

GET /federation/user/{username}
Accept: application/activity+json

请求带有 ActivityStreams Accept 头的参与者 IRI 返回参与者文档。 Owncast 将自身视为一个 ActivityStreams Service(而不是 Person)。 其格式为:

{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"type": "Service",
"id": "https://owncast.example.com/federation/user/live",
"preferredUsername": "live",
"name": "My Owncast Server",
"summary": "Server description / bio",
"url": "https://owncast.example.com/federation/user/live",
"published": "2023-01-01T00:00:00Z",
"manuallyApprovesFollowers": false,
"discoverable": true,
"inbox": "https://owncast.example.com/federation/user/live/inbox",
"outbox": "https://owncast.example.com/federation/user/live/outbox",
"followers": "https://owncast.example.com/federation/user/live/followers",
"icon": {
"type": "Image",
"mediaType": "image/png",
"url": "https://owncast.example.com/logo/external?uc=..."
},
"image": {
"type": "Image",
"url": "https://owncast.example.com/logo/external?uc=..."
},
"tag": [
{
"type": "Hashtag",
"name": "#owncast",
"href": "https://owncast.directory/tags/owncast"
}
],
"attachment": [
{
"type": "PropertyValue",
"name": "Website",
"value": "<a href=\"...\">...</a>"
}
],
"publicKey": {
"id": "https://owncast.example.com/federation/user/live#main-key",
"owner": "https://owncast.example.com/federation/user/live",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
}
}

与互操作实现相关的关键点:

  • 参与者 IRI 布局{server}/federation/user/{username},集合挂载在其上:{actor}/inbox{actor}/outbox{actor}/followers
  • following{actor}/following 请求,但始终返回 404 — Owncast 从不公开关注列表。
  • manuallyApprovesFollowers 反映了服务器是否处于 私有 联邦模式。 当设置为 true 时,不会自动接受关注。
  • discoverable 始终为 true(使用 toot: 命名空间语义)。
  • 公共密钥 位于 {actor}#main-key,是一个以 PEM (PKIX) 形式表示的 RSA-2048 密钥,用于验证服务器的 HTTP 签名。

HTTP 签名

Owncast 签署其出站请求并使用 “Signature” HTTP 头方案验证入站请求(draft-cavage HTTP 签名,在 Fediverse 中使用)。

验证 Owncast 发往您的请求

当 Owncast 将活动 POST 到您的收件箱时,它包含:

  • 一个 Signature 头,带有 keyId="{actor}#main-key"algorithm="rsa-sha256" 和签名的 headers 列表。
  • 签名的头涵盖 (request-target)hostdatedigest
  • 一个 Digest 头,包含请求体的 SHA-256 摘要。
  • Content-Type: application/activity+json 和一个格式为 {version}; https://owncast.onlineUser-Agent

要验证:获取 keyId 处的参与者,读取 publicKey.publicKeyPem,并验证签名和主体摘要。

为您发送到 Owncast 的请求签名

Owncast 验证每个传递到其收件箱的活动上的签名:

  1. 它解析您 Signature 头中的 keyIdalgorithmkeyId 必须https:// URL。
  2. 它解析您的参与者并获取您的公钥。
  3. 它检查您密钥所在域是否不在实例的阻止域列表中,以及该参与者本身是否没有被阻止。
  4. 它验证签名,尝试使用声明的算法,然后回退到 rsa-sha256rsa-sha512
  5. 它验证 Digest 头与请求体是否匹配。
  6. If your request carries a parseable Date header, it must be close to the server's clock: a date more than 1 hour in the past or more than 1 hour in the future is rejected. This bounds replay of captured, validly-signed requests. A missing or unparseable Date skips the check.

实际上,这意味着:使用 RSA 密钥签名 (request-target) host date digest,在参与者的 publicKey 字段中发布该密钥,包含 SHA-256 的 Digest,并通过 HTTPS 提供参与者。

Owncast 发送的活动(出站)

所有出站活动都源自服务器参与者,并交付给关注者的收件箱(优先考虑关注者宣传的 sharedInbox)。 公共活动发送到 https://www.w3.org/ns/activitystreams#Publicto 中,并在 cc 中包含关注者集合;在私有模式下,只发送给关注者集合。

活动对象时间发送到
CreateNote流开始直播(“开始直播” 消息);其他公共帖子关注者(+ 公共)
UpdateService服务器资料(名称、头像、简介等) 更改关注者
关注参与者 IRI操作员关注另一个 Owncast 服务器(特色流转)目标服务器
Offer服务器 URL在直播期间定期发送,作为流 “ping”目录关注者
Accept入站 Follow作为收到的 Follow 的响应关注者
Rejectinbound Follow当操作员删除了列出该服务器的目录时该目录
离开server URLThe stream ends (the offline counterpart to Offer)Directory followers
Undo关注An operator unfeatures an Owncast server they previously followedThe target server
Accept / Rejectinbound QuoteRequestIn response to a received QuoteRequestThe requester

创建 / 备注 — 开始直播

最重要的活动。 当流开始直播时,Owncast 发送一个 Create,包裹一个 NoteNote 包含 HTML content(可配置的开始直播消息、流标题、主题链接和指向服务器的链接)、Hashtag 标签,并在可用时附加一个 Image 附件,其中包含流预览(preview.gifthumbnail.jpg)。 如果服务器标记为 NSFW,该备注将携带 sensitive: true。 主题链接到 https://owncast.directory/tags/{tag},并始终附加一个 #owncast 标签。

这是大多数消费者关心的活动:通过关注参与者订阅,然后在收件箱中查看 Create/Note 活动以了解流何时开始。

Offer / 流 ping(出站)

这是 Owncast 的一个扩展,支持 特色流 / 小型目录 功能。 在线时,服务器定期发送 Offer 活动,其 object 是服务器 URL,并携带 Owncast 自定义元数据(流状态、标题、描述、服务器名称、徽标、标签)。 它让接收目录无需轮询即可保持其直播流列表的新鲜。 The matching offline signal is the Leave activity, sent when the stream ends. Owncast 仅将 OfferLeave 发送到将自己识别为目录的关注者(请参阅 自定义命名空间),从不发送给普通的粉丝关注者。

更新、关注、接受

  • Update 当服务器档案元数据更改时,发送给关注者,以便远程缓存刷新。
  • Follow 当操作员关注另一个 Owncast 服务器时发送。 服务器随后期待收到 Accept(或 Reject)。
  • 接受 在服务器处于公共(自动批准)模式时,会自动发送以响应传入的 关注

Owncast 接收的活动(传入)

通过向演员的 收件箱 发送已签名的活动进行传递。 The inbox returns 202 Accepted immediately and processes the activity asynchronously, so a 202 only means the activity was queued, not that it was acted on. Owncast signature-verifies and dispatches each one from that queue.

活动处理
Follow存储关注者;在公共模式下自动批准并返回 接受(在私人模式下待批准)。 带有 ns#directory 标记的关注始终被手动审批,无论模式如何,并且不发出关注事件。 否则发出 FediverseEngagementFollow 事件。
撤消关注删除关注者。
喜欢对本地对象记录一个参与。 发出 FediverseEngagementLike
宣布对本地对象进行提升/转发。 记录一个参与并发出 FediverseEngagementRepost
接受关注将我们关注的远程 Owncast 服务器标记为已接受(特色流动)。
拒绝关注将我们对远程服务器的关注标记为被拒绝。
提供来自其他 Owncast 服务器的流 ping。 如果它携带 streamStatus: "live",Owncast 将在其联邦服务器表中标记该服务器为在线,并存储流媒体元数据。
Leave提供 相关的离线部分:标记远程 Owncast 服务器的流离线。
UpdatePerson更新现有关注者的存储元数据(显示名称、收件箱、共享收件箱、头像)。 Updates with any other object type are ignored.
CreateNoteAccepted when the object is a single Note attributed to the signing actor and the note is either a reply to a post this server published or addressed to the actor (a mention). Raised as an event for the operator and plugins, not added to any timeline. See inbound posts.
QuoteRequestA FEP-044f request to quote one of the server's posts. Accepted only for locally authored posts while federation is public and quotes are enabled. Answered with Accept or Reject. See quote requests.

两个重要保护:

  • 参与年龄限制。 仅当引用对象不超过 36 小时 时,才会记录 喜欢宣布 活动。 较旧的参与会被忽略。 这使参与通知与近期流媒体保持关联。
  • 阻止和 SSRF。 被阻止的域/演员的传入活动在签名验证期间被拒绝。 传出的交付拒绝非 HTTPS 和内部/环回收件箱 URL。

Inbound posts (Create)

Inbound fediverse posts require Owncast v0.3.0

Earlier releases rejected every inbound Create. Owncast 0.3.0 accepts the narrow cases described here.

Owncast accepts a Create whose object is exactly one Note attributed to the same actor that sent it. It keeps two kinds of notes: replies to a post the server itself published, and notes that address the actor directly (mentions). Everything else is ignored. An accepted post is raised as a reply or mention event for the operator's integrations and plugins. It is not added to a timeline, is not shown to viewers, and is never re-federated, so there is still no public conversation surface on an Owncast server.

Quote requests (FEP-044f)

Quote requests require Owncast v0.3.0

QuoteRequest handling is new in Owncast 0.3.0.

A remote user asking permission to quote one of the server's posts sends a FEP-044f QuoteRequest whose object is the post being quoted and whose instrument is the quote post itself. Owncast accepts the request only when the quoted object is a post this server authored, federation is in public mode, and the operator has quoting enabled. On accept it stores a QuoteAuthorization stamp as a dereferenceable object and replies with an Accept whose result is the stamp's IRI, so any server can fetch the stamp to verify the quote was approved. Every other case gets a Reject, which clears the pending quote on the remote end.

服务器到服务器的活动

提供离开接受拒绝 一起形成 Owncast 到 Owncast 的 "特色流" 协议。 如果您正在构建想要参与的目录或聚合器,模式是:

  1. 发送一个 关注,设置 ns#directory 标记(见 自定义命名空间)到 Owncast 服务器的演员。 操作员手动批准,然后期待一个 接受
  2. 在服务器在线时接收定期的 提供 活动(带有 Owncast 元数据)。
  3. 当流结束时接收一个 离开

如果您不需要实时直播 ping,您同样可以只消费标准的 创建/注释 上线帖子。

Owncast 自定义命名空间

Owncast 在命名空间 https://owncast.online/ns# 下添加一小组自定义 JSON-LD 属性。 流媒体元数据属性显示为 提供(和相关的服务器到服务器)活动中的额外顶级字段,让接收者从单个活动填充目录条目。 ns#directory 标记会出现在一个 关注 上,并标识发送者为目录。 如果您只关心标准 ActivityPub,所有都是可选的并且可以安全忽略。

属性类型意思
https://owncast.online/ns#streamStatus字符串"直播""离线"。 总是在服务器到服务器的活动中存在。
https://owncast.online/ns#streamTitle字符串当前流媒体标题,当设置时。
https://owncast.online/ns#streamDescription字符串服务器摘要/描述。
https://owncast.online/ns#serverName字符串可人读的服务器名称。
https://owncast.online/ns#logoUrl字符串服务器徽标的绝对 URL。
https://owncast.online/ns#thumbnailUrl字符串当前流媒体缩略图的绝对 URL。
https://owncast.online/ns#streamTags字符串数组服务器元数据标签。
https://owncast.online/ns#directory布尔值关注 中设置为 true,以将发送者标识为目录。

目录通过在发送的 关注 上设置 ns#directorytrue 来标识自己。 该标记,且仅该标记,使 Owncast 将关注视为目录列表:它将该关注挂起等待操作员批准,一旦批准,将 提供离开 流 ping 发送给该关注者。 上面的流媒体元数据字段仅为描述性,不能单独识别目录。

构建 Owncast 流媒体目录

The server-to-server activities that power Owncast's own featured streams feature are open for you to consume. 如果您想运行一个跟踪哪些 Owncast 服务器在线的目录或聚合器,您可以像任何 Fediverse 演员那样关注每个服务器,然后响应其发送的实时信号。

有关完整的可运行示例参考,请参见 owncast-directory-example 存储库。 这是一个小型 Python 应用程序,实现了本节中的所有内容:一个已发布的演员,ns#directory 关注,提供/离开/拒绝 处理,以及一个列出在线服务器的网页。 将其视为起点,而不是生产服务。

您需要一个已发布的演员和签名请求,和任何关注者一样(请参见 HTTP 签名)。 从那里:

  1. 发送一个签名的 关注,将 https://owncast.online/ns#directory 设置为 true(请参见 自定义命名空间),到每个服务器的演员。 该标记将您标识为目录,这使得服务器向您发送其流 ping,并使得被列出为选择加入:Owncast 服务器始终将目录关注挂起等待其操作员手动批准,无论该服务器的联合隐私配置如何。 您不会收到任何状态,直到操作员批准,因此请期待每个条目保持待处理状态,直到每一个被选择加入。 没有标记的 关注 被视为普通的粉丝关注:它可能会被自动接受,但永远不会收到 提供/离开 ping。
  2. 当服务器在线时,它每五分钟大约会向您的收件箱发出一次 提供,携带 Owncast 自定义元数据:流状态、标题、描述、服务器名称、徽标、缩略图和标签。 根据这些字段创建或刷新该服务器的目录条目。
  3. 当流顺利结束时,服务器会发布一个 离开。 将条目标记为离线。
  4. 如果服务器的操作员从他们的一侧删除您的目录,服务器将发布您原始 关注拒绝。 放弃该条目:您不再被授权列出该服务器,它将停止向您发送 ping。

没有内置的流程可以让 Owncast 服务器请求在您目录中的位置,因此组装列表是您这边的工作。 让操作员选择加入的简单方法是在您的目录上放置一个提交表单,操作员可以在其中输入其服务器 URL。 您和您的目录决定列出哪些提交,哪些拒绝。 当您接受一个时,以与上面相同的方式关注该服务器。 操作员批准关注,提交者会期待这样做,将关注、接受和 ping 流列出其流。

将这些 ping 视为心跳。 如果服务器在没有 离开 的情况下停止发送 提供 活动,因为崩溃、失去连接或被防火墙阻止,没什么能积极告诉您它已关闭。 过期任何您没有听到几个 ping 间隔的条目。 Owncast 自己的目录在错过两个 ping 后将一个对等方标记为离线,大约 11 分钟,每分钟运行一次过期检查。

一些值得注意的事项:

  • 元数据字段来自远程服务器,因此将其视为不受信任的输入。 在渲染之前限制长度并确认任何 URL 是 httphttps。 您可以信任的值是您选择关注的服务器 URL,而不是服务器发送的显示名称。
  • 缩略图和徽标 URL 是稳定的,因此浏览器会缓存它们。 如果您希望预览保持当前,则在刷新条目时附加一个变化的缓存破坏查询。
  • 您根本不必使用这些 ping。 如果您只需要知道服务器何时在线,而不需要保持对当前在线者的实时查看,则关注该演员并观察标准的 创建/注释 上线帖子,像任何其他 Fediverse 消费者一样。

要使您的服务被识别为目录,请在您发送的 关注 上将 https://owncast.online/ns#directory 设置为 true。 看到这个的服务器会将关注留给它的操作员,且一旦获得批准,将向您发送其流媒体 ping。

端点参考

所有路径相对于服务器的基本 URL。 每个端点在禁用联合时返回 405

路径方法目的
/.well-known/webfingerGET解析 acct: → 演员 IRI
/.well-known/host-metaGETXRD 指针到 WebFinger
/.well-known/nodeinfoGETNodeInfo 发现文档
/nodeinfo/2.0GETNodeInfo 2.0 服务器元数据
/.well-known/x-nodeinfo2GETx-nodeinfo2 服务器元数据
/api/v1/instanceGET与 Mastodon 兼容的实例描述
/federation/user/{username}GET服务演员文档
/federation/user/{username}/inboxPOST将活动传递到服务器
/federation/user/{username}/outboxGET服务器已发送活动的集合
/federation/user/{username}/followersGET分页关注者集合
/federation/user/{username}/followingGET始终为 404(没有关注列表)
/federation/{object-id}GET获取单个存储的 ActivityPub 对象

构建兼容应用程序 — 清单

要从您自己的应用程序中关注和消费一个 Owncast 流:

  1. 解析 句柄与 WebFinger(acct:live@host)以获取演员 IRI,然后使用 Accept: application/activity+json 获取演员。
  2. 发布您自己的参与者,并提供一个 publicKey,通过 HTTPS 服务,具有可达的 inbox
  3. 向参与者的收件箱发送签名的 Follow。 用 RSA 签名 (request-target) host date digest 并包含 SHA-256 Digest
  4. 处理 Owncast 发送到您收件箱的 Accept(公共模式)或等待手动批准(私人模式)。
  5. 监听上线帖子Create/Note 活动到达您的收件箱,告诉您流媒体已开始;alternate/application/x-mpegURL WebFinger 链接为您提供播放的 HLS URL。
  6. 可选充当目录:在您的 Follow 上设置 https://owncast.online/ns#directorytrue,让操作者批准,然后消费 Offer/Leave ping 和 https://owncast.online/ns#* 元数据,以获得实时活动性和更丰富的目录条目。
  7. 验证 Owncast 发送给您的所有内容的签名与参与者的 #main-key

Owncast accepts replies and mentions only as notifications for the operator and plugins: they are never threaded, displayed, or re-federated, and the server exposes no following list. Design your integration around following + notifications + likes/boosts rather than two-way conversation.


Improve this page

See something missing or incorrect? Edit this page and improve the documentation for everyone.

Contributors to this documentation