博客更新与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账户然后做应用关联,以后登录状态进入未初始化的文章后就会进行初始化,相应的在repoIssue里也会有相应文章标题的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认证,请求时必须要有 clientIDclientSecret ,这种方式每小时的 Rate limiting60 。如果只是发表评论,肯定是够了。

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

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

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

  • Token description :描述,随意
  • Select scopes:必选的选项为repo下的repo:statusrepo_deploymentpublic_repo

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

关于MD5

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

如果脚本中不进行MD5加密,那么创建的Issuelabel就会是原始的文章地址,而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初始化