summaryrefslogtreecommitdiff
path: root/content/article/webmention.md
blob: 78cf44337dc0566067ed8547df04d7cbf0603721 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
+++
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。<!--more-->

## 什么是 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 博客的 `<head>` 标签,找到 `rel="webmention"` 的 `<link>` 标签。

```HTML
<head>
    <link rel="webmention" href="https://alice.blog/webmention">
</head>
```

这是标准的,声明 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/),也就是表明你是这个网站的主人,怎么做到呢?

在网站的 `<head>` 添加:

```HTML
<link rel="me" href="https://github.com/alice">
<link rel="me" href="https://twitter.com/alice">
<link rel="me" href="mailto:alice@mail.com">
```

把 `alice` 改成你自己的用户名。这个标记的意思是,这个网站的主人有 GitHub、Twitter 和电子邮箱账号,用户名或地址如上。接下来,确保你的 Twitter 账号页面或 GitHub 账号资料里有链接到这个网站。这样一来,你就证明了自己拥有这些账号,IndieAuth 也就允许你用这些账号登录。你不必配置全部三个账号,只需要一个有效的登录方式。如果是邮箱的话,IndieAuth 就会给你发送一封电子邮件确认登录。

你还可以把 `<link>` 写成 `<a>` 标签,如果网页里已经有这些账号页面的链接了,只需要给 `<a>` 加上 `rel="me"` 即可。这种标记方式叫作 microformats,后文还会提到。

配置好 IndieAuth 之后,在 [webmention.io](https://webmention.io/) 输入域名登录,接下来会进入仪表盘。现在,你需要在 `<head>` 里面指定 webmention.io 作为接收器,添加这段内容:

```HTML
<head>
    <link rel=webmention href=https://webmention.io/你的域名/webmention>
</head>
```

接下来 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
<div id="webmentions"></div>
<script src="/path/to/webmention.min.js" async></script>
```

具体的使用方式见项目文档。

如果能力允许,可以自己编写渲染逻辑,毕竟 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
<form action="https://alice.blog/webmenion" method="POST">
    <input type="url" name="source">
    <input type="hidden" name="target" value="{{ .Permalink }}">
    <button type="submit">发送</button>
</form>
```

其中:

- `action` 是 POST 请求要发送到的地方,这里填写你的 Webmention 端点
- `<input name="source">` 是用户输入的,对方文章的 URL
- `<input name="target">` 是你这篇文章的 URL,一般由程序自动生成;这里写的 `{{ .Permalink }}` 是 Hugo 的页面永久链接,你应该把它替换成你的博客软件的变量。

你需要改的就只是 `action` 的地址和 `<input name="target">` 的 `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 结构完全不同。有的可能是这样的:

```HTML
<main id="container">
    <h1 id="post-title">Title</h1>
    <article id="post-content">
        <p>Content</p>
        <!-- ... -->
        <p>Author is ..., follow me on <a href="https://mastodon.social/@author">Mastodon</a></p>
    </article>
    <div id="comment">
        <!-- ... -->
    </div>
</main>
```

也有可能是这样的:

```HTML
<main id="container" class="flex justify-center items-center my-10 mx-auto p-4">
    <header class="max-h-lg shadow-lg">
        <div class="rd overflow-hidden">
            <img src="/posts/xxx/banner.jpg" class="block">
        </div>
        <h1 class="font-extrabold text-3xl">Title</h1>
        <p class="font-semibold text-xl">Subtitle</p>
    </header>
    <article class="text-md leading-loose prose">
        <div id="post-content">
            <!-- ... -->
        </div>
        <div id="post-endnotes">
            <ul>
                <!-- ... -->
            </ul>
        </div>
        <div id="author-info" class="bg-white px-4 py-6 my-2 shadow">
            <p>Author Name</p>
            <p>Description</p>
            <a href="https://mastodon.social/@author">Mastodon</a>
        </div>
    </article>
    <footer>
        <!-- ... -->
    </footer>
</main>
```

偶尔还能见到一些莫名其妙的 HTML 结构,比如我就见过这样的:

```HTML
<div id="scroll-body">
    <div id="contant-wrap-wrap">
        <div id="content-wrap">
            <div id="main-content">
                <div id="page-content">
                    <h1 id="page-title">...</h1>
                    <p>...</p>
                </div>
            </div>
        </div>
    </div>
</div>
```

~~简直有大病。~~ 无论如何,尽管它们的结构不同,但包含的内容是相似的,换句话说,**数据结构**是相似的。他们都有可能是一篇文章,包含页面标题、内容、作者信息等等,这篇内容同时还有可能是另一篇内容的回应、点赞或转发。

被浏览器渲染为图形过后,人读起来会理所应当地觉得这些就是相同的数据,但在机器看来就难了。HTML 结构有无限种可能,要怎么让机器快速且准确地判断某个网页属于什么类型的内容,它包含了哪些结构化的数据?

其实已经有一些方案被提出了,比如被广泛使用的 [Open Graph 协议](https://ogp.me/)。网页只要按照标准,添加一些 `<meta>` 标签,就可以使其成为一个数据对象。Facebook 等社交媒体,在遇到这些链接的时候,会爬取 Open Graph 的内容,生成更具视觉效果的卡片。

以下是 Open Graph 的一个例子:

```HTML
<meta property="og:title" content="The Rock" />
<meta property="og:type" content="video.movie" />
<meta property="og:url" content="https://www.imdb.com/title/tt0117500/" />
<meta property="og:image" content="https://ia.media-imdb.com/images/rock.jpg" />
```

microformats 是类似的东西,但他没有规定新的 `<meta>` 标签类型,也不像另一个标准 [JSON-LD](https://www.w3.org/TR/json-ld11/) 一样要求插入一大段包含 JSON 的 `<script>` 标签。它就建立在最基础的 HTML 之上。

以下是用 microformats 标记的一篇博客文章示例。

```HTML
<article class="h-entry">
  <h1 class="p-name">Microformats are amazing</h1>
  <p>Published by <a class="p-author h-card" href="http://example.com">W. Developer</a>
     on <time class="dt-published" datetime="2013-06-13 12:00:00">13<sup>th</sup> June 2013</time></p>

  <p class="p-summary">In which I extoll the virtues of using microformats.</p>

  <div class="e-content">
    <p>Blah blah blah</p>
  </div>
</article>
```

注意到 `class` 的值了吗?`p-name` 标记了这篇博客文章的名字,`p-author` 标记了作者,`p-summary` 标记了这篇文章的总结摘要,`e-content` 则是这篇文章的内容。最外层的 `h-entry`,标记这是一个条目,博客文章、社交媒体发帖和维基页面等等,都可以用这个类名标记。

要实现 microformats 标准,你不需要在其他地方添加任何别的格式的数据,只需要在原有的 HTML 上操作就好了。刚刚我们看到的是 [h-entry](https://microformats.org/wiki/h-entry) 格式,用来标记发布在万维网上的内容,除此之外,还有用来标记人和组织的 [h-card](https://microformats.org/wiki/h-card),用来标记日历和事件的 [h-event](https://microformats.org/wiki/h-event) 等等。

我们刚才用来配置 IndieAuth 的 `<link rel="me">` 标签,也是一个 [microformats 格式](https://microformats.org/wiki/rel-me)。它更常见的用法是直接标记在 `<a>` 标签上:

```HTML
<a href="https://github.com/tantek" rel="me">@t</a>
<a href="https://tantek.com/" rel="me">https://tantek.com/</a>
```

`rel="me"` 本身不是用来配置 IndieAuth 的,只是标记网站站长的其他网页链接的一种方式。IndieAuth 只是使用了 microformats 的这个格式而已。

总而言之,`microformats` 是名副其实的「微格式」,不需要对网站结构做出太大改动,就可以标记结构化的数据。

### 和 Webmention 的关系

我猜测除了 [webmention.io](https://webmention.io/) 之外,没有太多接收端实现了解析 microformats 的能力(不过,[Weepinbell](https://codeberg.org/eltrac/weepinbell) 可以!)。最初 Webmention 仅仅是「A 网页提及了 B 网页」的通知,并没有什么复杂的分类,但 microformats 的使用让一切都不简单了起来。

一般来说,相互提及的网页都是博客文章,文章是用 microformats 的 `h-entry` 标记的。这个格式除了标记基本的元信息之外,还有这样的属性:

- `u-in-reply-to`:这个 `h-entry` 回复了哪篇文章
- `p-rsvp`:这个 `h-entry` 是对什么的 RSVP,即表明自己是否要参与某次聚会、活动或事件
- `u-like-of`:这个 `h-entry` 是对哪篇文章的喜欢(点赞)
- `u-repost-of`:这个 `h-entry` 是对哪篇文章的转发

比如,Bob 回应了 Alice 的文章,它在文档开头提及了 Alice 的文章,它可能是这样写的:

```HTML
<div class="h-entry">
    <h1 class="p-name">对 Alice 文章的回复</h1>
    <div class="e-content">
        <p>前几天我在 Alice 的博客上读到了
            <a class="u-in-reply-to" href="https://alice.blog/">
                一篇有趣的文章
            </a>,
            我很受启发。</p>
        <p>对这个问题,我也有相似的思考。</p>
        <!-- ... -->
    </div>
</div>
```

你注意到了吗?Bob 把文章中提及的链接标记为 `u-in-reply-to`。如此一来,这篇文章在能够解析 microformats 的程序看来,就是对 Alice 文章的一篇回应。如果是 `u-like-of`,就是对那篇文章的点赞,`u-repost-of` 就是转发。

至于 `p-rsvp`,我觉得这个设计有点大病——真的会有人写网页公开发 RSVP 吗?这种东西难道不应该私下交流吗?无论如何,它是存在的,你可以用 microformats 标记 RSVP,也意味着你可以用 Webmention 来发 RSVP 通知他人你是否决定出席某次活动。

具体而言,Webmention 和 microformats 这两个标准是这样一起使用的:支持解析 microformats 的 Webmention 接收端,在收到请求时,除了做最基本的检查之外,还会解析 `source` 网页中的 microformats,如果有的话,就一起保存下来。

IndieWeb 上的居民会根据 `h-entry` 是否包含 `u-in-reply-to` 等属性,决定展示 Webmention 的方式。如果有 `u-in-reply-to`,就显示为回复或评论;如果有 `u-like-of`,就显示为点赞;如果有 `u-repost-of`,就显示为转发。以下是 Barnaby Walters 的网站实现这一展示方式的例子。

![](https://img.eltr.ac/geedeapro/article/webmention/800px-waterpigs-webmention-display.png "Barnaby 的[网站](http://waterpigs.co.uk/)")

如果都没有,那就当作普通的提及,也就是最原始的 Webmention。

### 在 Webmention 中显示自己的名字和头像

上一小节解释了 `h-entry` 格式,用来标记内容。用来标记作者时,可以用到 `h-card` 格式。

```HTML
<a class="h-card" href="https://tantek.com/">Tantek Çelik</a>,

<span class="h-card">
  <a class="p-name p-org u-url" href="https://microformats.org/">microformats.org</a>
</span>
```

`h-card` 本来是独立的,用来标记人和组织的格式,但它也可以作为 `h-entry` 的子属性 `p-author`,放在 `h-entry` 里,标记这个内容的作者。

```HTML
<div class="h-entry">
    <div class="p-author h-card">
      <img src="https://aaronpk.com/images/aaronpk.jpg" class="u-photo" width="40">
      <a href="https://aaronpk.com/" class="u-url p-name">Aaron Parecki</a>
    </div>
</div>
```

这样,其他软件在解析 microformats 的时候,就能发现你的名字和头像了。一般来说也会展示在 Webmention 里。

## 同步来自联邦宇宙的提及

你可能怀疑会不会真的有人这样用 microformats,给链接标记 `u-in-reply-to` 这样的类名总归有点麻烦,大部分时候我们只是简单提及一篇文章而已,不见得就是对那篇文章的直接回应,或者说点赞和转发。这些东西更像是社交媒体上会用到的。

嗯?社交媒体?

既然我们在讨论开放的标准,那就不得不提及开放且去中心化的社交媒体了。是的,我说的就是[联邦宇宙](https://jointhefediverse.net/?lang=zh-hans)。有没有办法把来自联邦宇宙的点赞、回复和转发,以 Webmention 的方式同步到网站上呢?

答案是使用 [Brid.gy](https://brid.gy/),这个服务允许你用 Mastodon 账号(以及其他兼容 Mastodon API 的联邦宇宙软件)登录,并将社交媒体账号与你的网站桥接起来。它做的事情很简单:

1. 每隔一段时间扫描你的社交媒体时间线,以及其他人与你的互动。
2. 如果发现你的某篇帖文包含你的网站上的 URL,而又有人点赞、回复或转发了这篇帖文,那么就视作这个人点赞、回复或转发了你网站上对应的这篇文章。
3. 创建一个占位 HTML 页面,包含用 microformats 标记的 `h-entry`,以这个占位页面作为 `source`,给你网站上的页面发送 Webmention。(如果你直接访问这个占位页面,会被跳转到实际的联邦宇宙帖子,但 Webmention 接收器会把这个占位页面视作真实的 `source` 地址)

如此一来,你的网站就通过 Webmention 和联邦宇宙互通了。

![](https://img.eltr.ac/geedeapro/article/webmention/bridgy-dashboard.png "Bridgy 的后台,列出了联邦宇宙上的互动,其中的一些已经作为 Webmention 发送")

我的网站就是这样做的,拉到页面最下方,你就能看到来自联邦宇宙的点赞和回应,不过我还没想好应该如何展示「转发」这个类型的 Webmention。

## 自己动手写 Webmention 接收端

自己写 Webmention 接收器并不困难!你只需要把 [Webmention 文档](https://www.w3.org/TR/webmention/)中有关接收端的部分读一遍,知道需要注意哪些技术细节[^1][^2]就好了。你只需要提供一个用于接收 Webmention 的端点,至于查询 Webmention 的 API,以及要不要解析和储存 microformats,都可以自己决定。

你完全可以只写一个给自己用的 Webmention 接收端,接收到 Webmention 请求后就通过聊天机器人发送给自己,不做展示也不做过多的处理,甚至不用储存。Webmention 尽管有规定删除和更新 Webmention 的方法(如果 `source` 有更新或者删除了引用,重新发送一次 Webmention 就应该能够更新),但并没有说一定要储存和展示 Webmention。

你也可以把 Webmention 的功能做得很丰富,用来代替评论系统。无论如何,如果你决定自己写 Webmention 接收端的话,这都取决于你,只要保持最基础的标准就好了。

你也可以看看 [Salmention](https://indieweb.org/Salmention),比起 Webmention,它只是增加了多级回应的功能。比如 Chris 看到 Bob 回应 Alice 的文章之后,也写了一篇文章回应 Bob,并给 Bob 发送了 Salmention。如果 Alice 的网站也支持 Salmention 的话,Alice 就会收到 Chris 的 Salmention,尽管 Chris 只是直接提及了 Bob。不过,这个拓展标准很少有人用。

## 资源

### 相关阅读

- [给博客添加 Webmention - Taxodium](https://taxodium.ink/add-webmention-to-blog.html)
- [在 Hexo 项目中添加 Webmention - Cytrogen](https://blog.cytrogen.icu/posts/1de)
- [一次对 Webmention 的探索 - 映屿](https://www.glowisle.me/posts/exploring-webmention/)
- [用 Webmention 接收 Mastodon 互动](https://fanrongbin.com/hugo-webmention-bridgy-mastodon/)
- [WebMention - 谭淞宸的知识库](https://notes.tansongchen.com/WebMention/)
- [Webmention - IndieWeb](https://indieweb.org/webmention)
- [Webmention - W3C Recommendation](https://www.w3.org/TR/webmention/)
- [microformats](https://microformats.org/)

### 工具

- [IndieWebify.Me](https://indiewebify.me/):加入 IndieWeb 的逐步教程
- [webmention.rocks](https://webmention.rocks/):测试网站能否正常接收 Webmention

## 最后

希望有更多的人能用上 Webmention,一起愉快地写博客交流。

我写累了,一会儿见吧。

[^1]: 比如,W3C 规定,为了避免 DoS 攻击,所有的 Webmention 必须异步处理,也就是验证 `source` 和 `target` 都是正确的 URL 之后,应该异步地向 `source` 发送 GET 请求,检查 `source` 的内容是否包含 `target`。

[^2]: 顺带一提,Webmention 其实不止支持 HTML,还可以提及 JSON 文档和纯文本文档。不过,虽然标准是这么写的,但应该不会有太多接收端很好地支持非 HTML 文档。