本文主要介绍基于Github Issues的轻量级博客评论系统Utterances。文章前半部分主要介绍Utterances的配置与使用,后半部分主要介绍如何利用近年来流行的Serverless化平台Cloudflare Workers自托管Utterances。
Utterances简介
Utterances是一个基于Github Issues的轻量级评论系统,可用于博客、Wiki等。它具有以下优点:
- 开源
- 不追踪,无广告,始终免费
- 所有的数据都存储在Github Issues
- 样式基于Github的Primer设计语言
- 夜间模式
- 轻量级;原生TypeScript;在“常青树”浏览器上不使用网络字体,JavaScript框架或Polyfill。
快速上手
- 在GitHub上新建一个公开仓库(Repository),安装Utterances GitHub App至该仓库。
-
在你的网页需要插入Utterances评论的位置,粘贴以下代码(username,reponame分别修改为你的GitHub用户名,仓库名)。
1
2
3
4
5
6
7<script src="https://utteranc.es/client.js" repo="username/reponame" issue-term="pathname" theme="github-light" crossorigin="anonymous" async> </script>
- 刷新网页就可以看到Utterances评论框了。
如果想进一步配置或者自托管Utterances,可以继续看下面的内容。
配置
下文将介绍以下代码中repo
,issue-term
,label
,theme
几个选项的配置。
1 |
|
repo
:设置存放评论的仓库
Utterances 使用Github Issues存储评论,所以需要一个仓库。你可以新建一个公开仓库专门用来放评论,也可以使用原有的仓库。要设置存放评论的仓库只需要将repo="username/reponame"
这一行中的username
改为你的GitHub用户名,reponame
改为你的仓库名,其它不变。
仓库需满足以下条件:
- 仓库必须为公开仓库,私有仓库访客无法查看对应Issues上的评论。
- 确保在仓库中安装了Utterances的GitHub App,或是你自己注册的GitHub App(自托管),否则用户将无法发表评论。
- 如果你的仓库是派生(fork)出的,请在仓库的
Settings
选项确认Features
区Issues
已勾选。
issue-term
:博客文章和Issue映射
Utterances使用以下几种规则建立博客文章和GitHub Issues的映射:
- Issue标题包含页面路径名(
issue-term="pathname"
)
Utterances将查询issue标题是否包含博客文章的路径名(pathname)。如果未找到匹配的issue,则当有人首次对你的博客文章发表评论时,Utterances会自动创建一个以此文章路径名为标题的issue。 - Issue标题包含页面URL(
issue-term="url"
)
Utterances将查询issue标题是否包含博客文章的URL
。如果未找到匹配的issue,则当有人首次对你的博客文章发表评论时,Utterances会自动创建一个以此文章URL
为标题的issue。 - Issue标题包含页面标题(
issue-term="title"
)
Utterances将查询issue标题是否包含博客文章的标题。如果未找到匹配的issue,则当有人首次对你的博客文章发表评论时,Utterances会自动创建一个以此文章标题为标题的issue。 - Issue标题包含页面og:title(
issue-term="og:title"
)
Utterances将查询issue标题是否包含博客文章的Open Graph标题(og:title)。如果未找到匹配的issue,则当有人首次对你的博客文章发表评论时,Utterances会自动创建一个以此文章og:title
为标题的issue。 - 特定的issue编号(
issue-number="具体数字"
)
Utterances按issue编号加载特定的issue。Utterances不会自动创建issue。 - Issue标题包含特定项(
issue-term="你设置的特定内容"
)
Utterances将查询issue标题是否包含你设置的特定项。如果未找到匹配的issue,则当有人首次对你的博客文章发表评论时,Utterances会自动创建一个以你设置的特定项为标题的issue。
label
:Issue标签
如果你使用原有的仓库,但是担心Issues页面评论和问题混杂在一起,Utterances支持设置标签(Label)来区分它们。设置label="你的标签内容"
,Utterances将在创建issue时使用你设置的标签。
- 标签名区分大小写。
- 标签必须存在于你的仓库中(须提前在GitHub Issues页面创建好,不能使用不存在的标签)。
-
标签名支持Emoji。例如:
1
label="💬"
theme
:主题
Utterances有多种主题,其中包括多款夜间模式主题。
- GitHub Light:
theme="github-light"
- GitHub Dark:
theme="github-dark"
- GitHub Dark Orange:
theme="github-dark-orange"
- Icy Dark:
theme="icy-dark"
- Dark Blue:
theme="dark-blue"
- Photon Dark:
theme="photon-dark"
你可以在文章末尾处的下拉框中选择主题以查看效果,点击此处跳转到文末。
自托管
自托管Utterances主要包含以下两个项目的部署:
- Utterances:前端静态网站,评论系统的界面显示,部署在GitHub Pages
- utterances-oauth:后端API,主要功能是授权,鉴权和创建Issues,部署在Cloudflare Workers
准备环境
- Debian、Ubuntu或是其他Linux发行版(教程里用的是Debian 10)
- GitHub账号(用于申请GitHub App及创建仓库等)
- Cloudflare账号(用于申请Cloudflare Workers)
- 域名(可选,用于解决第三方Cookie的问题,详情见FAQ:如何解决第三方Cookie的问题)
为方便说明配置,教程中有以下假设,如有雷同,纯属巧合。
- GitHub用户名:
example
- GitHub Pages域名:
example.github.io
- Cloudflare Worker子域名:
example.workers.dev
- 博客域名:
blog.example.com
- Utterances部署域名:
utterances.example.com
- utterances-oauth部署域名:
api.utterances.example.com
你需要替换教程中的相关关键字。
注册GitHub App
打开https://github.com/settings/apps/new注册自己的GitHub App,仅需要填写以下内容,其它默认:
项目 | 值 |
---|---|
GitHub App name | 你的博客的名字 |
Description | 你的博客的描述 |
Homepage URL | 你的博客的网址 |
User authorization callback URL | utterances-oauth部署域名,以/authorized 结尾。例如:https://api.utterances.example.com/authorized 。如果你使用Cloudflare Worker子域域名,应为:https://utterances-oauth.example.workers.dev/authorized |
Webhook URL | 必填项,但是Utterances不使用此项。随意填,例如你的博客网址。 |
Repository permissions | 仅Issues: Read & Write ,不需要其它权限。 |
Where can this GitHub App be installed | 仅此账号(Only on this account) |
注册成功后,系统会提示你生成私钥(Generate a private key),点击生成(这是必须的步骤,否则无法使用GitHub App)。但是Utterances不使用私钥,无需记住其值。
记下Client ID
和Client secret
的值,后面会用到它。
在Cloudflare Workers上托管utterances-oauth
利用近年来流行的Serverless平台Cloudflare Workers,你可以方便的部署utterances-oauth。免费版每天提供10万次请求,足够一般使用了。
构建utterances-oauth
-
克隆(
clone
)utterances-oauth1
2git clone https://github.com/utterance/utterances-oauth cd utterances-oauth
-
安装依赖
1
yarn install
-
配置环境变量
在utterances-oauth根目录下新建一个名为
.env
的环境变量配置文件,配置如下变量:- BOT_TOKEN:创建GitHub Issues时将使用的GitHub个人访问令牌(Scopes仅需勾选
public_repo
,点此生成)。 - CLIENT_ID:GitHub OAuth web application flow中要使用的
Client ID
,即注册GitHub App时记住的Client ID
的值。 - CLIENT_SECRET:OAuth web application flow中要使用的
Client secret
,即注册GitHub App时记住的Client secret
的值。 - STATE_PASSWORD:32位密码,用于加密request headers/cookies中的
state
,点此生成。 - ORIGINS:来源域(origin)列表。多个来源域以逗号分隔,用于跨域资源共享Cross-Origin Resource Sharing(CORS)。
示例如下(替换
*
号内容为你自己的值,ORIGINS网址改为部署Utterances的网址):1
2
3
4
5BOT_TOKEN=**************************************** CLIENT_ID=******************** CLIENT_SECRET=**************************************** STATE_PASSWORD=******************************** ORIGINS=https://utterances.example.com,http://localhost:4000
- BOT_TOKEN:创建GitHub Issues时将使用的GitHub个人访问令牌(Scopes仅需勾选
-
执行命令
yarn run build
构建utterances-oauth,编译后的文件位于dist/index.js
。
部署utterances-oauth
本教程提供三种部署的方法:
- 网页端:最简单,按照页面说明即可轻松部署。适合喜欢图形化用户界面(GUI)的用户
- cfworker:cfworker是Cloudflare Workers的一个功能强大的工具集,由Utterances作者开发。适合喜欢命令行界面(CLI)的用户
- GitHub Actions:通过GitHub Actions持续部署。适合没有本地环境,也不方便安装的人。通过GitHub Actions提供的开发环境,下载、构建、部署utterances-oauth
推荐使用第三种通过GitHub Actions部署的方法,这样每次版本更新的时候只需要push
更新的内容到GitHub上,GitHub Actions就会替我们自动部署,一劳永逸。
通过Cloudflare Workers网页端部署
登录网页版Cloudflare,进入Workers页面新建一个Worker,将生成的dist/index.js
文件内容复制到Script框内(删掉默认生成的代码),修改Worker名称为utterances-oauth
,点击Save and Deploy(保存并部署),并配置好路由等即可。
通过cfworker部署
cfworker是Cloudflare Workers的一个功能强大的工具集,由Utterances作者开发,其提供了直接部署的方法,需要你在.env
文件中添加以下变量
- CLOUDFLARE_EMAIL:注册Cloudflare时的邮箱
- CLOUDFLARE_API_KEY:部署Cloudflare Workers需要的全局API Key(Global API Key)
- CLOUDFLARE_ZONE_ID:你的Cloudflare Zone ID,在你的域名的预览页(Overview)
- CLOUDFLARE_ACCOUNT_ID:你的Cloudflare Account ID,在你的域名的预览页(Overview)
- CLOUDFLARE_WORKERS_DEV_PROJECT:你的Cloudflare Worker项目名,例如
utterances-oauth
。项目名必须满足以下要求:- 以字母开头
- 以字母或数字结尾
- 仅包含字母,数字,下划线和连字符
- 不超过63个字符
1 |
|
在utterances-oauth根目录下的package.json
文件中找到此行代码
1 |
|
修改为以下内容,注意域名后的/*
要保留。
1 |
|
如果你使用Cloudflare Worker子域名,则应为:
1 |
|
执行命令:
1 |
|
通过GitHub Actions部署
通过GitHub Actions部署不需要.env
的环境变量配置文件,任何时候绝不能将此文件上传到公开GitHub仓库,所有涉及密码等信息的值会通过GitHub 加密密码来存储。
-
在此仓库新建一个
cloudflare-workers.yml
文件,此文件位于仓库根目录的.github/workflows/
目录下。文件内容如下: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
65name: Deploy utterances-oauth on: push: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [14] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - name: Install Dependencies run: yarn install # Add .env before build - name: Add .env run: | cat > .env <<EOF BOT_TOKEN=$BOT_TOKEN CLIENT_ID=$CLIENT_ID CLIENT_SECRET=$CLIENT_SECRET STATE_PASSWORD=$STATE_PASSWORD ORIGINS=$ORIGINS EOF env: BOT_TOKEN: ${{ secrets.UTTERANCES_BOT_TOKEN }} CLIENT_ID: ${{ secrets.UTTERANCES_CLIENT_ID }} CLIENT_SECRET: ${{ secrets.UTTERANCES_CLIENT_SECRET }} STATE_PASSWORD: ${{ secrets.UTTERANCES_STATE_PASSWORD }} ORIGINS: https://utterances.example.com - name: Build run: yarn run build # Add wrangler.toml required by Wrangler - name: Add wrangler.toml run: | cat > wrangler.toml <<EOF name = "utterances-oauth" type = "javascript" routes = [ "api.utterances.example.com/*" ] account_id = "$ACCOUNT_ID" zone_id = "$ZONE_ID" EOF env: ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} ZONE_ID: ${{ secrets.CF_ZONE_ID }} # Deploy to Cloudflare Workers with Wrangler - name: Deploy to Cloudflare Workers with Wrangler uses: cloudflare/wrangler-action@1.2.0 with: apiToken: ${{ secrets.CF_API_TOKEN }}
- 按教程“配置环境变量”小节,修改第40行
ORIGINS
的值,修改第52行routes
的值。 -
打开GitHub上派生后的仓库,在仓库名称下,单击Settings(设置),在左侧边栏中,单击 Secrets(密码)添加密码,详细步骤见为仓库创建加密密码。
需要添加如下几个密码:
- UTTERANCES_BOT_TOKEN
- UTTERANCES_CLIENT_ID
- UTTERANCES_CLIENT_SECRET
- UTTERANCES_STATE_PASSWORD
- CF_ACCOUNT_ID
- CF_ZONE_ID
- CF_API_TOKEN
具体密码的值参考“构建utterances-oauth”和“通过cfworker部署”这两个章节中的环境变量的值。
其中CF_API_TOKEN为Cloudflare API Tokens,可在Cloudflare处生成API令牌模板,
API tokens templates
选择Edit Cloudflare Workers
,以限制此API Tokens权限为仅能编辑Cloudflare Workers。 - 将修改后的文件
push
到GitHub仓库上。之后每当你push
更新到master
分支时,GitHub Actions会自动将代码部署到Cloudflare Workers。
测试
用浏览器打开https://api.utterances.example.com
,如果你使用Cloudflare Workers子域名则打开https://utterances-oauth.example.workers.dev
,如果看到页面显示alive
字样,则说明utterances-oauth你已经部署成功。
如果你想本地测试的话执行命令yarn run start
。
在GitHub Pages上托管Utterances
-
克隆你刚刚派生的仓库(修改
example
为你的GitHub用户名):git clone https://github.com/example/utterances
-
从
package.json
文件中找到此行代码
"predeploy": "yarn run build && touch dist/.nojekyll && echo 'utteranc.es' > dist/CNAME",
修改为"predeploy": "yarn run build && touch dist/.nojekyll",
注意最后有逗号,
-
修改
src/utterances-api.ts
文件中的网址为你的utterances-oauth网址1
export const UTTERANCES_API = 'https://api.utterances.example.com';
如果你使用Cloudflare Workers子域域名则修改如下:
1
export const UTTERANCES_API = 'https://utterances-oauth.example.workers.dev';
-
按照教程配置所述,修改
src/index.html
文件中如下部分的src
,repo
,issue-term
等配置项。记得在存放评论的仓库上安装你注册的GitHub App。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<if condition="NODE_ENV === 'production'"> <script src="https://utteranc.es/client.js" repo="utterance/utterances" issue-term="homepage" crossorigin="anonymous" async> </script> </if> <else> <script src="http://localhost:4000/client.js" repo="jdanyow/utterances-demo" issue-term="pathname" crossorigin="anonymous" async> </script> </else>
例如修改
client.js
为部署到GitHub Pages的链接:
src="https://example.github.io/utterances/client.js"
等 -
执行
yarn run deploy
部署到GitHub Pages。
部署后打开你Utterances项目的GitHub Pages页面https://example.github.io/utterances/
,在该页面底部你可以测试评论等功能,测试完毕后你就可以按照“快速上手”,“配置”等教程上线你的博客评论功能了。
如果你不想使用GitHub Pages,只需将第6步改为执行yarn run build
,并将/dist
文件夹下生成的所有文件上传到其他支持静态网页的平台即可。
如果你想本地测试的话执行命令yarn run start
。
FAQ
博主为什么选择 Utterances
之前一直想为博客添加评论系统,刚开始考虑的是国外的Disqus,因为在国内无法访问,所以查找过一些使用Disqus API的方法,如DisqusJS和Disqus PHP API,但是它们都需要后端程序,功能上也有一些局限性。之后偶然发现有人用Github Issues存储博客评论,如:Gitment、Gitalk以及本文介绍的Utterances,都是基于Github Issues开发的评论系统。
目前Gitment已经很久没更新了,Gitalk和Utterances都在持续更新。其中Utterances使用Primer设计语言,这是Github官方使用并开源的设计指南,所以Utterances有和Github Issues类似的漂亮样式。Utterances使用了更精细化的权限管理(详见FAQ:Utterances需要哪些权限),这也是笔者最后选择Utterances的原因之一。
Utterances的原理
Gitment、Gitalk和Utterances的原理都是基于GitHub Issues自带的评论功能,在访问博客时通过GitHub API查询对应博文下Issues的评论,显示在自己的网页上,评论时访客登录Github账号,授权应用,在对应GitHub Issues下发布评论。
Utterances需要哪些权限
Utterances通过GitHub App来操作GitHub Issues。不同于OAuth Apps,GitHub App提供了精细化的权限管理,且GitHub App仅能作用于安装了它的仓库,闲情见GitHub App与OAuth Apps区别。
当你点击登录(Sign in to commnet)时,GitHub会有一个界面显示你需要授权GitHub App哪些权限。其中“确定你和GitHub App都可以访问的那些资源(Determine what resources both you and GitHub App can access)”这句里的“资源”指的是你GitHub账号权限和GitHub App权限的交集。由于我们在注册GitHub App时仓库权限仅勾选了Issues: Read & Write
,所以Utterances仅能在安装了它的仓库上读写该仓库的Issues。
如何解决第三方Cookie的问题
Chrome浏览器自80版本开始默认设置SameSite=Lax
。越来越多的用户也开始打开“阻止第三方Cookie”的选项。
Utterances仅使用Cookie保存登录信息以避免重复登录的问题,如果你使用Utterances GitHub App的话会存在Cookie跨域问题,这也是很多人选择自托管的原因之一。
要解决这个问题,你需要将博客和Utterances-outh部署在同一域名下。目前Chrome、Firefox、Microsoft Edge Chromium 把同一域名下的子域Cookie视为第一方Cookie。
假如你的博客网址是https://blog.example.com
,那么Utterances-outh可以部署在https://api.example.com
或是https://api.utterances.example.com
下。
参考
- Self hosting instructions
- Cloudflare Workers Configure
- utterances requires too many permissions
- Why does MicrosoftDocs require GitHub “Resources” permissions?
- Authenticating with GitHub Apps
- What can GitHub Apps and OAuth Apps access?
在下面的下拉框中选择Utterances的主题以查看效果,点击此处返回theme:主题
章节。