Content
Author is ..., follow me on Mastodon
+++ title = 'Webmention 简明指南' slug = 'webmention' tags = ['IndieWeb','Web'] categories = '代码炼金术' date = 2026-03-10 23:04:43 draft = false toc = true toot = 'https://akk.eltr.ac/objects/46ba3e0a-3b8e-4d84-bcc3-e10eebe87397' +++ [Webmention](https://www.w3.org/TR/webmention/) 是一个 W3C 推荐标准,[IndieWeb](https://indieweb.org/) 很喜欢这个标准,甚至制订了名为 [Salmention](https://indieweb.org/Salmention) 的拓展,只可惜 Webmention 本身就没什么人用,太小众了,这个拓展标准更是没多少人跟进和实现。对于独立博客来说,这项技术其实相当有用,实现起来也不复杂,但中文博客中支持发送和接收 Webmention 的很少,中文资料也几乎没有。 本文意在解释什么是 Webmention、如何使用它,以及如何让自己的网站支持 Webmention。 ## 什么是 Webmention? 在即时聊天软件和社交媒体中,用户可以使用 `@用户名` 的格式提及另一个用户,对方会收到通知,知道他被提及了。 这是相当有用的功能,只可惜一般的提及功能是局限于某个平台上的,没办法跨平台通知,比如,如果你使用 Telegram,就没办法提及 QQ 上的用户。Webmention 虽然不是用来解决这个问题的,但它的确提供了一种分布式社交的能力,允许某人在一个网站上提及另一个网站上的内容。 假设 Alice 发布了一篇文章,URL 是 `https://alice.blog/interesting-post`,而 Bob 读到之后觉得很不错,在自己的网站上写了一篇文章,可能是回应,可能是表达喜爱,也可能只是简单地提及了,Bob 的这篇文章的 URL 是 `https://bob.site/cool-stuff`。 这便是 Webmention 的应用场景。如果 Alice 的网站支持 Webmention,那么 Bob 就可以向 Alice 的网站发送 Webmention,这样 Alice 就知道她写的内容被提及了,她也可以把 Bob 文章的链接展示在网页下,让其他人也知道 Bob 写了一篇回应。 这个过程是如何发生的呢? Webmention 其实很简单,**它只是 HTTP 请求**。首先,Bob 要找到 Alice 网站用于接收 Webmention 的端点,向这个地址发送 POST 请求,请求包含两个值,`source` 和 `target`——前者是 Webmention 的源地址,也就是 Bob 的文章,Webmention 是从他的网站发送过来的;后者是目标地址,也就是 Alice 的文章,是 Webmention 要发送到的地方。 如果你不懂什么是 HTTP 请求,可以这样理解:当你在一个博客的评论区填写名字、邮箱地址和评论内容并点击发送按钮之后,你就向这个网站接收评论的端点发送了一条 HTTP 请求,准确来说,是 POST 类型的 HTTP 请求,而 Webmention 也是相同的请求,不过请求发送到的端点不同,请求的内容也不同。 Alice 的服务器通过这个端点接收到了 HTTP 请求之后,就会检查这是不是有效的 Webmention。有效性的要求很低,只要 `source` 和 `target` 都是有效的 URL,`source` 真的包含了 `target` 链接(即真的提及了 `target`),就视作有效,没有其他格式要求。接收到 Webmention 之后要怎么处理,W3C 没有做规范,完全取决于接收端怎么实现。 这里有必要说明一下,Webmention 只是技术**标准**,而不是具体的软件。就像你可以使用不同的邮件客户端发送电子邮件,发送出去的邮件其他人用不同的客户端也能正常查看,就是因为电子邮件是开放标准,而不是具体的软件。 没什么好讲的了,Webmention 就是这么简单:一个网页提及了另一个网页,这个网站向对方网站发送 POST 请求来通知对方,对方网站接收这个请求。 ## 如何发送 Webmention? 你不需要对自己的网站做任何修改就能够发送 Webmention,因为它只是一个 POST 请求。接收端不会验证请求的来源,仅仅是验证 `source` 和 `target` 两个 URL,所以你可以从任何地方发送请求。 比如,使用 `curl`: ```shell curl \ -d 'source=https://bob.site/cool-stuff&target=https://alice.blog/interesting-post' \ https://alice.blog/webmention ``` 注意这里的 `https://alice.blog/webmention`,这是 Alice 博客的 Webmention 端点,也就是 Webmention 要被发送到的地方。要找到这个端点很简单,只需要检查 Alice 博客的 `
` 标签,找到 `rel="webmention"` 的 `` 标签。 ```HTML ``` 这是标准的,声明 Webmention 端点的方式,也是其他人知道 Alice 的网站支持 Webmention 的判断依据。向这个地址发送 POST 请求,Alice 的 Webmention 接收器就会收到请求。 不过,一般没有人会用命令行发送 Webmention,这太不友好了。很多支持 Webmention 的博主会在网站上放一个输入框和一个按钮,表示你可以在这里输入你的文章 URL,然后点击发送,对方就能接收到 Webmention 了。这个表单做起来非常简单,我会在下一节「如何接收 Webmention 讲到」。 只要是能够发送 POST 请求的方式,都能够用来发送 Webmention。一般来说,发送 Webmention 的过程会被自动化,每当有一篇新文章被发现,就检查这篇文章里包含的外部链接,然后逐个请求这些外部链接,检查它们有没有声明 Webmention 端口,如果有,就向这个端口发送 Webmention。 ### 发送 Webmention 的自动化工具 [webmention.app](https://webmention.app/) 提供了发送 Webmention 的工具,最简单的方式是使用 API。假设你在一篇 URL 为 `https://my.site/post-xxx` 的文章里包含了一些外链,你想向这些链接发送 Webmention,那么你可以向这个地址发送 POST 请求: ``` POST https://webmention.app/check/?url=https://my.site/post-xxx ``` 这样就通知了 webmention.app,让它帮你检查你这篇文章里包含了哪些外链、哪些支持 Webmention,然后向有接收端的链接发送 Webmention。你可以在他们的首页最下方的输入框输入你的 URL 并点击「START」,来测试自动发送。 如果要做到全自动,那也很简单。最方便的情况是:你有一个 RSS 订阅源。这样,就可以用 [IFTTT](https://ifttt.com/) 创建一个工作流,在 RSS 更新时通知 webmention.app 检查你的新文章,并帮你发送 Webmention。具体见[这个教程](https://webmention.app/docs#using-ifttt-to-trigger-checks)。 如果你不想依赖 IFTTT 这样的服务,那就要根据你的网站架构来决定实现方式了。 如果你的网站是动态的,使用 WordPress 或 Typecho 等动态博客软件构建,那么你可以找一找有没有实现了 Webmention 的插件可以使用。我找到了 [WordPress 插件](https://wordpress.org/plugins/webmention/),其他的软件需要读者自行搜索。 如果你的网站是静态的,使用 Hugo、Astro、Hexo、11ty 等静态网站生成器构建,那么你可以参考[我给 Hugo 添加自动发送 Webmention 能力](https://jar.geedea.pro/notes/automate-outgoing-webmentions/)的方法。我用到了 `@remy/webmention` 这个 NPM 包,是命令行工具,在网站构建完成后用这个命令行工具扫描一遍 RSS 源,即可向文中提及的外部链接发送 Webmention。不必担心多次构建会重新发送 Webmention 的问题,接收端要是多次收到了相同的 Webmention,会做查重处理,这是标准里规定了的。 具体怎么在构建完成后自动执行这个命令,要看你使用的静态网站生成器。如果是基于 Node.js 开发的,比如 Hexo,**可能**会有类似这样的写法: ```json "scripts": { "postbuild": "webmention public/index.xml --limit 1 --send" }, ``` 其他可以参考的链接: - [docs :: webmention.app](https://webmention.app/docs#using-the-command-line) - [Sending webmentions from a static site - localghost](https://localghost.dev/blog/sending-webmentions-from-a-static-site/) - [Automatically sending Webmentions from a static website - James Mead](https://jamesmead.org/blog/2020-10-13-sending-webmentions-from-a-static-website) 如果觉得太难了,不配置自动发送也可以。手动发送反而更方便把控什么时候发送,什么时候不发送,毕竟有的时候可能不想要打扰别人。 ## 如何接收 Webmention? 显然,你需要一个 Webmention 接收器。如果你的博客是用 WordPress 构建的,前文提到的[插件](https://wordpress.org/plugins/webmention/)已经具备了接收 Webmention 的功能。如果是其他博客软件,可能要考虑使用第三方 Webmention 接收器,或者自己部署接收器了。 ### 使用 webmention.io 最简单也是最常用的第三方 Webmention 接收服务是 [webmention.io](https://webmention.io/),我也在使用。这个服务是免费且开源的,所以还算值得信赖。要使用他们的服务,你首先需要配置 [IndieAuth](https://indieauth.com/),也就是表明你是这个网站的主人,怎么做到呢? 在网站的 `` 添加: ```HTML ``` 把 `alice` 改成你自己的用户名。这个标记的意思是,这个网站的主人有 GitHub、Twitter 和电子邮箱账号,用户名或地址如上。接下来,确保你的 Twitter 账号页面或 GitHub 账号资料里有链接到这个网站。这样一来,你就证明了自己拥有这些账号,IndieAuth 也就允许你用这些账号登录。你不必配置全部三个账号,只需要一个有效的登录方式。如果是邮箱的话,IndieAuth 就会给你发送一封电子邮件确认登录。 你还可以把 `` 写成 `` 标签,如果网页里已经有这些账号页面的链接了,只需要给 `` 加上 `rel="me"` 即可。这种标记方式叫作 microformats,后文还会提到。 配置好 IndieAuth 之后,在 [webmention.io](https://webmention.io/) 输入域名登录,接下来会进入仪表盘。现在,你需要在 `` 里面指定 webmention.io 作为接收器,添加这段内容: ```HTML ``` 接下来 webmention.io 就能够帮你接收 Webmention 了。这个服务是 [Aaron Parecki](https://aaronparecki.com/) 提供的,如果你觉得不错,可以去支持他!他还是 IndieWebCamp 的创始人和 OAuth 工作组的编辑者。 好了,现在你能接收 Webmention 了,数据存放在 webmention.io 的服务器里,要怎么查看接收到的 Webmention 呢?有这样几种方式: 1. 在[仪表盘](https://webmention.io/dashboard)查看最近的 Webmention。 2. 在[设置](https://webmention.io/settings)获取 RSS/Atom 订阅源,使用 RSS 阅读器查看。 3. 配置 [Webhook](https://webmention.io/settings/webhooks),在有新 Webmention 时向指定 URL 发送 HTTP 请求,可以配合 [Bark](https://github.com/Finb/Bark) 或 [ntfy](https://ntfy.sh/) 推送到你的手机上。 4. 通过 [API](https://github.com/aaronpk/webmention.io#api) 获取数据。 如果你想通过 API 把 Webmention 展示在自己的网站上,最简单的方法是使用 [webmention.js](https://github.com/PlaidWeb/webmention.js/)。把 JavaScript 文件放在自己的网站上之后,在需要显示 Webmention 的地方添加: ```HTML ``` 具体的使用方式见项目文档。 如果能力允许,可以自己编写渲染逻辑,毕竟 API 已经有了。 ### 自托管 Webmention 接收端 webmention.io 很好用,配合 webmention.js,甚至不需要自己写一行代码,不需要部署任何服务,就能接收并展示 Webmention,这对大部分人来说已经够了。不过,如果你很在乎自己的数据,并希望尽可能少依赖第三方服务,那么你大概要自己部署接收端了。 在可以自己部署的 Webmention 接收端中,使用者比较多的是 Horst Gutmann 维护的 [webmentiond](https://github.com/zerok/webmentiond/),用 Go 编写,存储基于 SQLite,简单轻量。此外还有用 Rust 编写的 [WesleyAC/webmention-receiver](https://github.com/WesleyAC/webmention-receiver) 和用 Python 编写的 [capjamesg/webmention-receiver](https://github.com/capjamesg/webmention-receiver)。 对于重复造轮子这件事,我们国人也不输(奇怪的比较)。[Chlorine](https://chlo.is/) 不久前用 Rust 写了一个 Webmention 接收端,名为 [CircleAt](https://codeberg.org/chlorine/circleat),而我也用一门 Lisp 方言开发了 [Weepinbell](https://codeberg.org/eltrac/weepinbell),最近才发布 [v0.1.0](https://codeberg.org/eltrac/weepinbell/releases/tag/v0.1.0) 版本。不过我不建议你用 Weepinbell,因为还没有正式投入使用,可能存在一些问题,进入稳定版本之后我会再发一篇文章的。当然,如果你愿意贡献的话,我将感激不尽。 在开始之前,我得警告读者,Webmention 作为一门小众技术标准,使用者很少,自托管接收器的更少,如果遇到问题,可能很难找到解决方案和能够提供帮助的人(虽然我很欢迎有人来问我啦)。如果对自己的技术没什么信心,webmention.io 已经很好了,比能够自托管的选项都要成熟不少。 不过剩下的我也没什么好讲的了,如果你决定自托管,那就选择一个自己觉得不错的接收端软件部署到服务器上。至于如何查看接收到的 Webmention 和如何展示,不同的接收端都有实现细节上的不同,这里略过。 ### 如何制作一个 Webmention 表单? 还记得我们说发送 Webmention 就是发送一个 POST 请求吗?想想还有什么是 POST 请求?一个网页上能够发送 POST 请求的最常见的元素是什么? 没错,就是表单。 ```HTML ``` 其中: - `action` 是 POST 请求要发送到的地方,这里填写你的 Webmention 端点 - `` 是用户输入的,对方文章的 URL - `` 是你这篇文章的 URL,一般由程序自动生成;这里写的 `{{ .Permalink }}` 是 Hugo 的页面永久链接,你应该把它替换成你的博客软件的变量。 你需要改的就只是 `action` 的地址和 `` 的 `value`,不需要写 JavaScript。不过,用户点击提交之后会被直接传送到 `action` 所指向的地址,如果你的 Webmention 接收端只返回 JSON,用户可能就不清楚到底有没有提交成功,还是有些不太友好的。如果你愿意的话,可以自己用 JavaScript 处理返回的数据,制作直观的提示,比如「发送成功」之类的。 放一个这样的表单在网页上并说明清楚作用,即便是不了解 Webmention 的访客也懂得如何使用。 ## 让 Webmention 生动起来:microformats [microformats](https://microformats.org/) 是一系列开放数据格式,建立在已被广泛采用的标准之上(比如 HTML)。它并不是和 Webmention 强绑定的,实际上 Webmention 标准完全没有提到过 microformats,只不过 IndieWeb 上的人很喜欢把它和 Webmention 一起用。说起来,应该是 webmention.io 的维护者 Aaron Parecki 带的头,webmention.io 默认支持解析 microformats。 由于没有 microformats 也不影响使用 Webmention,如果你不感兴趣的话,可以跳过这一节。 ### microformats 是什么? 回答这个问题之前,先要了解它解决了什么问题。 网页可以是任何东西,可以是 Web 应用(比如 Notion 这样的在线笔记),可以是小工具(比如计算器、格式化工具、视频下载器),可以是相册,也可以是一篇文章,可以是日历。同一张网页也可能包含各种各样的东西,一篇博客文章中有页面标题、主体内容、标签、评分、回复等等。 网页的具体结构也是不确定的,为了实现不同的排版和效果,不同网站的 HTML 结构完全不同。有的可能是这样的: ```HTMLContent
Author is ..., follow me on Mastodon
Subtitle
...