博客更新与Gitalk插件初始化

因为看原来的那个博客主题有些审美疲劳,因此决定换一个主题,虽然以前我比较嫌弃NEXT(可能是其他的场景太丑),不过现在看感觉Mist场景还挺好看的(真香)

除此之外还加入了Gitalk评论模块并搞了一个自动初始化文章的脚本,省的要一个一个点开初始化(虽然效果还达不到手动初始化那么好)。

Gitalk 模块

Gitalk is a modern comment component based on GitHub Issue and Preact.

Gitalk 是一个基于 GitHub Issue 和 Preact 开发的评论插件,NEXT 7主题已经集成了这个插件,因此配置起来要更容易一些,网上的教程很多,从**GitHub Application**到在theme的配置文件里加入代码段都很清楚,我就不展开了,直接进入初始化。

Gitalk 初始化

手动初始化

Gitalk是需要一个repo的,并且需要创建文章对应的Issue进行初始化。

在未创建Issue的情况下会出现以下内容:

因此我们需要对Gitalk进行初始化,手动的方法就是在页面登录GitHub账户然后做应用关联,以后登录状态进入未初始化的文章后就会进行初始化,相应的在repo的Issue里也会有相应文章标题的Issue出现,此文章的每一条评论都会这个Issue下面出现(Issue下面的评论就是文章下面的评论)。

那么就会有一个问题:如果一开始文章很多,加入了Gitalk后手动初始化就意味着要点开每一篇文章,这就很麻烦,因此我想到有没有脚本可以用。

脚本的出现

经过一番搜索,找到了两个node.js的脚本以及一些Ruby的脚本,因为我不会Ruby,所以选择了JS(虽然JS也不怎么会,但想到node.js用的是JS,还是决定用JS的脚本)。

站点地图

脚本是基于网站sitemap的批量刷新,关于sitemap我以前写过一个博文,主要内容差不多:Hexo添加Sitemap

Personal Access Token

因为需要调用GitHub API因此需要一个Personal Access Token以避免Rate limiting,GitHub API对一些接口有调用限制,具体可以查看 Rate limiting 的解释。对于不同的认证方式,调用限制不同。

Gitalk使用的是GitHub的OAuth认证,请求时必须要有 clientID 和 clientSecret ,这种方式每小时的 Rate limiting 是 60 。如果只是发表评论,肯定是够了。

而如果要批量创建所有文章对应的Issue来作为这些文章的评论存储,可能就未必够了。

好在GitHub提供了另一种认证方式——Personal access token,这种方式每小时的限制高达 5000 次。所以,第一步就是申请这个token。

从GitHub的 Personal access token 页面,点击 Generate new token 。

  • Token description :描述,随意
  • Select scopes:必选的选项为repo下的repo:status、repo_deployment和public_repo

创建完成后,获得一个Token,一定要保存这个Token

关于MD5

因为在配置Gitalk的时候在id一栏设置了对pathname进行MD5加密,因此在自动初始化的脚本中,也要相对的在label里对path进行MD5处理(虽然我程序里的变量名好像不是这个)。

如果脚本中不进行MD5加密,那么创建的Issue的label就会是原始的文章地址,而Gitalk并不识别这个Issue,还是需要手动初始化(两个Issue是不会合并的)。

因此,如果在Gitalk里设置了MD5,那么在脚本里同样要进行MD5,同一文件的MD5摘要是一样的。

代码依赖项

因为我借鉴了两篇文章给出的代码,因此需要结合一下他们的依赖项

1
2
3
4
npm install request --save
npm install xml-parser --save
npm install yamljs --save
npm install cheerio --save

其实主要还是第二篇的代码做的主体🤔

脚本文件

在Hexo的根目录创建文件gitalk_init.js,将下面的代码复制进去并修改相应的配置参数。其中token就是上面申请并保存的Token。这个token我前一天搞得第二天用不了了???🤔反正在搞一个就是了,没有token是无法使用API的,token错误会导致issues变量内容错误。

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
const request = require("request");
const fs = require("fs");
const path = require("path");
const url = require("url");
const xmlParser = require("xml-parser");
const YAML = require("yamljs");
const cheerio = require("cheerio");
const crypto = require('crypto');
// 根据自己的情况进行配置
const config = {
username: "YourGithubUserName", // GitHub 用户名
token: "YourPersonalAccessToken", // GitHub Token
repo: "YourGitalkIssueRepo", // 存放 issues的git仓库
// sitemap.xml的路径,commit.js放置在根目录下,无需修改,其他情况自行处理
sitemapUrl: path.resolve(__dirname, "./public/sitemap.xml"),
kind: "Gitalk", // "Gitalk" or "Gitment"
};
let issuesUrl = `https://api.github.com/repos/${config.username}/${config.repo}/issues?access_token=${config.token}`;

let requestGetOpt = {
url: `${issuesUrl}&page=1&per_page=1000`,
json: true,
headers: {
"User-Agent": "github-user"
}
};
let requestPostOpt = {
...requestGetOpt,
url:issuesUrl,
method: "POST",
form: ""
};

console.log("开始初始化评论...");

(async function() {
console.log("开始检索链接,请稍等...");

try {
let websiteConfig = YAML.parse(fs.readFileSync(path.resolve(__dirname, "./_config.yml"), "utf8"));

let urls = sitemapXmlReader(config.sitemapUrl);
console.log(`共检索到${urls.length-1}个链接`);

console.log("开始获取已经初始化的issues:");
let issues = await send(requestGetOpt);
console.log(`已经存在${issues.length}个issues`);

let notInitIssueLinks = urls.filter((link) => {
return !issues.find((item) => {
link = removeProtocol(link);
return item.body.includes(link);
});
});

for(let i=0;i<notInitIssueLinks.length;i++)
{
if(notInitIssueLinks[i].endsWith("tags/index.html"))
{
notInitIssueLinks.splice(i,1);
i--;
}
}

if (notInitIssueLinks.length > 0) {
console.log(`本次有${notInitIssueLinks.length}个链接需要初始化issue:`);
console.log(notInitIssueLinks);
console.log("开始提交初始化请求, 大约需要40秒...");
/**
* 部署好网站后,直接执行start,新增文章并不会生成评论
* 经测试,最少需要等待40秒,才可以正确生成, 怀疑跟github的api有关系,没有找到实锤
*/
setTimeout(async ()=>{
let initRet = await notInitIssueLinks.map(async (item) => {
let html = await send({ ...requestGetOpt, url: item });
let title = cheerio.load(html)("title").text();
let desc = item + "\n\n" + cheerio.load(html)("meta[name='description']").attr("content");
let pathLabel = url.parse(item).path;
let label = crypto.createHash('md5').update(pathLabel,'utf-8').digest('hex');
let form = JSON.stringify({ "body": desc, "labels": [config.kind, label], "title": title });
return send({ ...requestPostOpt, form });
});
console.log(`已完成${initRet.length}个!`);
console.log("可以愉快的发表评论了!");
},40000);
} else {
console.log("本次发布无新增页面,无需初始化issue!!");
}
} catch (e) {
console.log(`初始化issue出错,错误如下:`);
console.log(e);
} finally {

}
})();

function sitemapXmlReader(file) {
let data = fs.readFileSync(file, "utf8");
let sitemap = xmlParser(data);
return sitemap.root.children.map(function (url) {
let loc = url.children.filter(function (item) {
return item.name === "loc";
})[0];
return loc.content;
});
}

function removeProtocol(url) {
return url.substr(url.indexOf(":"));
}

function send(options) {
return new Promise(function (resolve, reject) {
request(options, function (error, response, body) {
if (!error) {
resolve(body);
} else {
reject(error);
}
});
});
}

我自己加了一个筛选文章的过程,不然连标签界面都会生成Issue。

1
2
3
4
5
6
if(notInitIssueLinks[i].endsWith("tags/index.html"))
{
notInitIssueLinks.splice(i,1);
i--;
}
}

确实是得等待40秒才可能生成正确的Issue。

执行脚本

代码写完以后在Git bash里输入node gitalk_init.js即可执行脚本。

当然如果想要部署博客的时候自动运行可以在Hexo根目录的package.json文件里加入

1
2
3
"scripts": {
"deploy": "hexo clean && hexo g -d && node gitalk_init.js"
}

之后直接输入npm run deploy即可直接执行。

后记

这其实是我第一次写JS代码,原来看的JS代码和别人写好的代码比起来真的是太简单了。。。JS的语言特性还不太习惯,比如函数调用和参数传递还有=>的使用,虽然靠啃**C/C++**的老底强行写了一下,但是还是有点吃力,主要功夫都花在了看懂代码,实际自己补的内容没几个字母。

一开始找sitemap读出来的内容找了半天,然后在一个错误的地方加了网址过滤导致后面传了空字符串出错,后来大概懂了一些以后才在半路把信息截下来。

中间还因为MD5的问题出现了自动加手动的Issue不在一起的问题,换了另一篇文章的代码改然后发现源码就是错的,对url的处理规则和自己的情况不一样,又要重写url处理,于是还是改回了第一个用的代码,找到那个加入MD5的地方。第一次改JS代码还是挺有挑战性的。

经过晚上的测试,Issue的内容能基本和手动的类似了,第一行显示url,第二行开始显示摘要,虽然不如手动的那么精确,不过还能勉强苟一下😅

自己写是写不出来的,只能改改代码勉强能用的样子。😕

参考链接

  1. 自动初始化Gitalk评论
  2. nodejs版本的Gitalk/Gitment评论自动初始化
  3. Linux下的自动化工具配置Gitalk初始化