需求:
1、用Gitalk实现评论功能
2、去除之前的Valine实现的评论功能
可在主题配置文件搜索comment system
,查看支持的评论系统;个人使用Gitalk进行配置;
Gitalk评论功能
1、注册OAuth application
- 在 github 中进行注册,进入 https://github.com/settings/profile
- 点击左侧 Developer settings
- Register a new application
Application name: #应用名称
Homepage URL: #网站URL(填自己的博客主页地址)
Application description #描述
Authorization callback URL: #网站URL(填自己的博客主页地址)
- 注册完成之后,会得到:
Client ID
和Client Secret
[1]
2、新建存放博客评论仓库
可以在 github 中建一个项目,专门用来存储你的博客评论
3、配置 Next 主题文件
编辑主题配置文件:themes\next\ _config.yml
,找到有关 gitalk的相关配置进行填写:
gitalk:
enable: true 开启gitalk评论,不需要配置
owner: github用户名
admin: github用户名
repo: 博客的仓库名称(注意不是地址)
ClientID: 上面生成的Client ID
ClientSecret: 上面生成的Client Secret
labels: 'gitalk' github issue 对应的issue标签(新建一个)
distractionFreeMode: true 无干扰模式,不需要更改
这是我的配置:
image
进入到 themes\next\layout\post.swig
(我的博客是基于 Next,如果有差异,替换路径中的 next 即可),添加 gitalk 模板文件的导入[2]:
<!-- {### Line 357,如果行数有差异,只需要在 POST END 文章结束后添加即可 ###} -->
{% if theme.git_talk.enabled and not is_index %}
<div>{% include 'git-talk.swig' %}</div>
{% endif %}
然后添加 git-talk.swig
文件(themes\next\layout\git-talk.swig
),文件内容如下:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css" />
<script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>
<div id="gitalk-container"></div>
<script type="text/javascript">
var gitalk = new Gitalk({
clientID: "{{theme.git_talk.clientID}}",
clientSecret: "{{theme.git_talk.clientSecret}}",
repo: "eminoda.github.io", // 博客仓库地址
owner: "eminoda", // github 用户名
admin: ["eminoda"], // github 用户名
perPage: 20,
id: location.pathname.slice(0, 50), // 查找 issus 的条件,后面将对 id 有针对逻辑
title: "{{page.title}}",
body: "🚀 " + location.href + "\n\n欢迎通过 issues 留言 ,互相交流学习😊", // 初始化后,issues 的内容
});
gitalk.render("gitalk-container");
</script>
以上操作完成后,打开文章即可看到以下页面,需要登录github账号初始化;每篇文章都需要进行登录初始化才可以使用;
未登录 初始化后4、全部文章批量初始化Issues
对于一个刚起步的博客站点没有任何问题,新增一篇文章,初始化下issue,顺手的事情。
但对于一个历史站点,里面可能有百篇文章,如果希望看到别人阅读的回复,则需要人工每篇进行初始化,不太现实,则需要程序来批量初始化。[2]
4.1 开启 OAuth 认证
需要在 Developer Setting 开启 Personal access tokens[3]
4.2 安装项目依赖
npm i request xml-parser blueimp-md5 moment hexo-generator-sitemap -S
需要的包:request、xml-parser、 blueimp-md5、 moment、 hexo-generator-sitemap
4.3 修改 hexo-generator-sitemap 配置
项目根目录配置文件 _config.yml
添加配置[4]:
#Sitemap
sitemap:
path: sitemap.xml
template: ./sitemap_template.xml
rel: false
tag: true
category: false
项目根目录新建文件 sitemap_template.xml
,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for post in posts %}
<url>
<loc>{{ post.permalink | uriencode }}</loc>
{% if post.updated %}
<lastmod>{{ post.updated.toISOString() }}</lastmod>
{% elif post.date %}
<lastmod>{{ post.date.toISOString() }}</lastmod>
{% endif %}
<date>{{ post.date }}</date>
<title>{{ post.title + ' | ' + config.title }}</title>
{# nunjucks 模版语法 https://github.com/mozilla/nunjucks #}
<desc>{{ post.description | default(post.excerpt) | default(post.content) | default(config.description) | striptags | truncate(200, true, '') }}</desc>
</url>
{% endfor %}
</urlset>
4.4 执行 hexo generate 命令,生成 sitemap
npm run build
此命令执行成功之后, public
目录下应该有生成 sitemap.xml
文件,如果没有此文件,请检查包是否安装成功。
4.5 添加自动初始化程序
项目根目录新建文件 talk-auto-init.js
,内容如下[5]:
const fs = require('fs');
const path = require('path');
const url = require('url');
const request = require('request');
const xmlParser = require('xml-parser');
const md5 = require('md5');
// 配置信息
const config = {
username: 'toimc', // GitHub repository 所有者,可以是个人或者组织。对应Gitalk配置中的owner
repo: "toimc.github.io", // 储存评论issue的github仓库名,仅需要仓库名字即可。对应 Gitalk配置中的repo
token: 'xxxxxx', // 前面申请的 personal access token
sitemap: path.join(__dirname, './public/sitemap.xml'), // 自己站点的 sitemap 文件地址
cache: true, // 是否启用缓存,启用缓存会将已经初始化的数据写入配置的 gitalkCacheFile 文件,下一次直接通过缓存文件判断
gitalkCacheFile: path.join(__dirname, './gitalk-init-cache.json'), // 用于保存 gitalk 已经初始化的 id 列表
gitalkErrorFile: path.join(__dirname, './gitalk-init-error.json'), // 用于保存 gitalk 初始化报错的数据
};
const api = 'https://api.github.com/repos/' + config.username + '/' + config.repo + '/issues';
/**
* 读取 sitemap 文件
* 远程 sitemap 文件获取可参考 https://www.npmjs.com/package/sitemapper
*/
const sitemapXmlReader = (file) => {
try {
const data = fs.readFileSync(file, 'utf8');
const sitemap = xmlParser(data);
let ret = [];
sitemap.root.children.forEach(function (url) {
const loc = url.children.find(function (item) {
return item.name === 'loc';
});
if (!loc) {
return false;
}
const title = url.children.find(function (item) {
return item.name === 'title';
});
const desc = url.children.find(function (item) {
return item.name === 'desc';
});
const date = url.children.find(function (item) {
return item.name === 'date';
});
ret.push({
url: loc.content,
title: title.content,
desc: desc.content,
date: date.content,
});
});
return ret;
} catch (e) {
return [];
}
};
// 获取 gitalk 使用的 id
const getGitalkId = ({
url: u,
date
}) => {
const link = url.parse(u);
// 链接不存在,不需要初始化
if (!link || !link.pathname) {
return false;
}
if (!date) {
return false;
}
return md5(link.pathname);
};
/**
* 通过以请求判断是否已经初始化
* @param {string} gitalk 初始化的id
* @return {[boolean, boolean]} 第一个值表示是否出错,第二个值 false 表示没初始化, true 表示已经初始化
*/
const getIsInitByRequest = (id) => {
const options = {
headers: {
'Authorization': 'token ' + config.token,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
'Accept': 'application/json'
},
url: api + '?labels=' + id + ',Gitalk',
method: 'GET'
};
return new Promise((resolve) => {
request(options, function (err, response, body) {
if (err) {
return resolve([err, false]);
}
if (response.statusCode != 200) {
return resolve([response, false]);
}
const res = JSON.parse(body);
if (res.length > 0) {
return resolve([false, true]);
}
return resolve([false, false]);
});
});
};
/**
* 通过缓存判断是否已经初始化
* @param {string} gitalk 初始化的id
* @return {boolean} false 表示没初始化, true 表示已经初始化
*/
const getIsInitByCache = (() => {
// 判断缓存文件是否存在
let gitalkCache = false;
try {
gitalkCache = require(config.gitalkCacheFile);
} catch (e) {}
return function (id) {
if (!gitalkCache) {
return false;
}
if (gitalkCache.find(({
id: itemId
}) => (itemId === id))) {
return true;
}
return false;
};
})();
// 根据缓存,判断链接是否已经初始化
// 第一个值表示是否出错,第二个值 false 表示没初始化, true 表示已经初始化
const idIsInit = async (id) => {
if (!config.cache) {
return await getIsInitByRequest(id);
}
// 如果通过缓存查询到的数据是未初始化,则再通过请求判断是否已经初始化,防止多次初始化
if (getIsInitByCache(id) === false) {
return await getIsInitByRequest(id);
}
return [false, true];
};
// 初始化
const gitalkInit = ({
url,
id,
title,
desc
}) => {
//创建issue
const reqBody = {
'title': title,
'labels': [id, 'Gitalk'],
'body': url + '\r\n\r\n' + desc
};
const options = {
headers: {
'Authorization': 'token ' + config.token,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
url: api,
body: JSON.stringify(reqBody),
method: 'POST'
};
return new Promise((resolve) => {
request(options, function (err, response, body) {
if (err) {
return resolve([err, false]);
}
if (response.statusCode != 201) {
return resolve([response, false]);
}
return resolve([false, true]);
});
});
};
/**
* 写入内容
* @param {string} fileName 文件名
* @param {string} content 内容
*/
const write = async (fileName, content, flag = 'w+') => {
return new Promise((resolve) => {
fs.open(fileName, flag, function (err, fd) {
if (err) {
resolve([err, false]);
return;
}
fs.writeFile(fd, content, function (err) {
if (err) {
resolve([err, false]);
return;
}
fs.close(fd, (err) => {
if (err) {
resolve([err, false]);
return;
}
});
resolve([false, true]);
});
});
});
};
const init = async () => {
const urls = sitemapXmlReader(config.sitemap);
// 报错的数据
const errorData = [];
// 已经初始化的数据
const initializedData = [];
// 成功初始化数据
const successData = [];
for (const item of urls) {
const {
url,
date,
title,
desc
} = item;
const id = getGitalkId({
url,
date
});
if (!id) {
console.log(`id: 生成失败 [ ${id} ] `);
errorData.push({
...item,
info: 'id 生成失败',
});
continue;
}
const [err, res] = await idIsInit(id);
if (err) {
console.log(`Error: 查询评论异常 [ ${title} ] , 信息:`, err || '无');
errorData.push({
...item,
info: '查询评论异常',
});
continue;
}
if (res === true) {
// console.log(`--- Gitalk 已经初始化 --- [ ${title} ] `);
initializedData.push({
id,
url,
title,
});
continue;
}
console.log(`Gitalk 初始化开始... [ ${title} ] `);
const [e, r] = await gitalkInit({
id,
url,
title,
desc
});
if (e || !r) {
console.log(`Error: Gitalk 初始化异常 [ ${title} ] , 信息:`, e || '无');
errorData.push({
...item,
info: '初始化异常',
});
continue;
}
successData.push({
id,
url,
title,
});
console.log(`Gitalk 初始化成功! [ ${title} ] - ${id}`);
continue;
}
console.log(''); // 空输出,用于换行
console.log('--------- 运行结果 ---------');
console.log(''); // 空输出,用于换行
if (errorData.length !== 0) {
console.log(`报错数据: ${errorData.length} 条。参考文件 ${config.gitalkErrorFile}。`);
await write(config.gitalkErrorFile, JSON.stringify(errorData, null, 2));
}
console.log(`本次成功: ${successData.length} 条。`);
// 写入缓存
if (config.cache) {
console.log(`写入缓存: ${(initializedData.length + successData.length)} 条,已初始化 ${initializedData.length} 条,本次成功: ${successData.length} 条。参考文件 ${config.gitalkCacheFile}。`);
await write(config.gitalkCacheFile, JSON.stringify(initializedData.concat(successData), null, 2));
} else {
console.log(`已初始化: ${initializedData.length} 条。`);
}
};
init();
以上代码需改动的地方:
image
修改博客根目录下的package.json
,新增命令:
"scripts": {
"talk": "node talk-auto-init.js"
},
注意观察文件格式,若放在最后一个,前面需要一个逗号,个人配置如下:
image
项目的 package.json 是配置和描述如何与程序交互和运行的中心。[6]
4.6 执行命令
npm run talk
若出现以下情况,则成功啦:
image
4.7 命令合并
修改 package.json
中的 build 命令,将自动初始化添加到 build 之后,这样每次执行 build 命令就会自动执行初始化命令。
"scripts": {
"build": "hexo generate && node talk-auto-init.js"
},
去除valine评论系统:
编辑themes\next\ _config.yml
文件:将enable选项改为false即可
某个页面要不要评论
可以单独关闭某个页面的评论,在页面的 Front-matter 中添加 comments 字段,设为 false。比如标签页不想要评论,则在标签页面中做如下设置[7]:
title: xxxxxxxxx
date: 2022-03-06 17:05:24
type: "tags"
comments: false
报错及解决
image修改package.json少了个逗号;
image原因:talk-auto-init.js
有误,之前借鉴的是这篇文章:hexo gitalk 评论自动初始化里的talk-auto-init.js
,造成错误,适用于我的是这篇文章的talk-auto-init.js
:hexo主题next中gitalk配置与评论初始化本文贴出的也是这篇talk-auto-init.js
;
解决方案:修改 talk-auto-init.js
:
修改之后还是有一些成功了,有一些还是报错,直接删除用第一篇的文章的talk-auto-init.js
,用第二篇文章的talk-auto-init.js
,再改个人配置即可;
网友评论