hexo-next-mastodon-comments-helper 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 feiju12138
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # Hexo NexT Mastodon Comments Helper
2
+
3
+ ![Package Version](https://img.shields.io/github/package-json/v/feiju12138/hexo-next-mastodon-comments-helper?style=flat-square)
4
+
5
+ Auto add `toot-id` in post header.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install hexo-next-mastodon-comments-helper
11
+ ```
12
+
13
+ ## Configure
14
+
15
+ Set the value `enable` to `true`, add the Mastodon instance domain (`MASTODON_DOMAIN`), and add the Mastodon instance user token (`MASTODON_TOKEN`). You can config those in both **hexo** or **theme** `_config.yml`:
16
+
17
+ ```yml next/_config.yml
18
+ # Mastodon Comments Helper
19
+ # For more information: https://github.com/feiju12138/mastodon-comments https://github.com/feiju12138/hexo-next-mastodon-comments https://github.com/feiju12138/hexo-next-mastodon-comments-helper
20
+ mastodon-comments-helper:
21
+ enable: false
22
+ MASTODON_DOMAIN: mastodon.social # Mastodon 实例域名
23
+ MASTODON_TOKEN: xxx # Mastodon 实例中具有写入权限的用户令牌
24
+ # TOOT_TEMPLATE: "我发布了一篇文章《{{postTitle}}》,一起来评论吧~" # 自定义嘟文模板
25
+ # REQUEST_PROXY: http://127.0.0.1:7890 # HTTP请求代理
26
+ ```
27
+
28
+ ## Just do it!
29
+
30
+ Every time you run `hexo s` or `hexo g`, it will automatically add `toot-id` to the post header.
31
+
32
+ It will add `toot-id` before `date`.
33
+
34
+ ```md source/_posts/Test.md
35
+ ---
36
+ title: Hello World
37
+ toot-id: "116164221651686918"
38
+ date: 2016-01-02 15:04:05
39
+ ---
40
+ ```
41
+
42
+ If `date` does not exist, it will add `toot-id` at the end.
43
+
44
+ ```md source/_posts/Test.md
45
+ ---
46
+ title: Hello World
47
+ toot-id: "116164221651686918"
48
+ ---
49
+ ```
package/default.yaml ADDED
@@ -0,0 +1,8 @@
1
+ # Mastodon Comments Helper
2
+ # For more information: https://github.com/feiju12138/mastodon-comments https://github.com/feiju12138/hexo-next-mastodon-comments https://github.com/feiju12138/hexo-next-mastodon-comments-helper
3
+ mastodon-comments-helper:
4
+ enable: false
5
+ MASTODON_DOMAIN: mastodon.social # Mastodon 实例域名
6
+ MASTODON_TOKEN: xxx # Mastodon 实例中具有写入权限的用户令牌
7
+ # TOOT_TEMPLATE: "我发布了一篇文章《{{postTitle}}》,一起来评论吧~" # 自定义嘟文模板
8
+ # REQUEST_PROXY: http://127.0.0.1:7890 # HTTP请求代理
package/index.js ADDED
@@ -0,0 +1,164 @@
1
+ /* global hexo */
2
+
3
+ "use strict";
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const yaml = require("js-yaml");
8
+ const rp = require("request-promise-native");
9
+ const deasync = require("deasync");
10
+
11
+ hexo.extend.filter.register("before_generate", function () {
12
+ const { log, config } = hexo;
13
+ const postsDir = path.join(hexo.base_dir, "source/_posts");
14
+
15
+ // 校验核心配置
16
+ const mastodonConfig = config["mastodon-comments-helper"] || {};
17
+ if (!mastodonConfig["MASTODON_DOMAIN"] || !mastodonConfig["MASTODON_TOKEN"]) {
18
+ log.error("[Mastodon Comments Helper] 请配置 mastodon-comments-helper.MASTODON_DOMAIN 和 mastodon-comments-helper.MASTODON_TOKEN");
19
+ return;
20
+ }
21
+
22
+ // 封装同步POST请求函数
23
+ function syncPostRequest(apiUrl, requestOptions) {
24
+ let requestDone = false;
25
+ let responseData = null;
26
+ let requestError = null;
27
+
28
+ // 构建 rp 的请求配置
29
+ const rpOptions = {
30
+ method: "POST",
31
+ url: apiUrl,
32
+ headers: requestOptions.headers || {},
33
+ body: requestOptions.json || {},
34
+ json: true,
35
+ timeout: requestOptions.timeout || 10000,
36
+ rejectUnauthorized: false,
37
+ };
38
+
39
+ // 添加上游配置的代理
40
+ if (mastodonConfig["REQUEST_PROXY"]) {
41
+ rpOptions.proxy = mastodonConfig["REQUEST_PROXY"];
42
+ log.info(`[Mastodon Comments Helper] 当前请求已使用代理访问: ${mastodonConfig["REQUEST_PROXY"]}`);
43
+ }
44
+
45
+ // 发起异步请求,通过闭包存储结果/错误
46
+ rp(rpOptions)
47
+ .then(res => {
48
+ responseData = res;
49
+ requestDone = true;
50
+ })
51
+ .catch(err => {
52
+ // 统一错误格式,兼容原代码的错误处理逻辑
53
+ if (err.statusCode) {
54
+ requestError = new Error(`API 请求失败,状态码: ${err.statusCode}, 响应内容: ${JSON.stringify(err.error || err.message)}`);
55
+ } else {
56
+ requestError = new Error(`请求异常: ${err.message}`);
57
+ }
58
+ requestDone = true;
59
+ });
60
+
61
+ // 阻塞线程直到请求完成
62
+ deasync.loopWhile(() => !requestDone);
63
+
64
+ // 有错误则抛出,无错误则返回响应数据
65
+ if (requestError) throw requestError;
66
+ return responseData;
67
+ }
68
+
69
+ // 遍历所有文章文件
70
+ const files = fs.readdirSync(postsDir).filter(file => file.endsWith(".md"));
71
+ for (const file of files) {
72
+ const filePath = path.join(postsDir, file);
73
+ const content = fs.readFileSync(filePath, "utf8");
74
+
75
+ // 分割 Front-matter 和正文
76
+ const [frontMatterBlock, ...contentRest] = content.split("---").slice(1);
77
+ if (!frontMatterBlock) {
78
+ log.warn(`[Mastodon Comments Helper] ${file} 无 Front-matter,跳过`);
79
+ continue;
80
+ }
81
+
82
+ // 解析 Front-matter
83
+ let frontMatter;
84
+ try {
85
+ frontMatter = yaml.load(frontMatterBlock);
86
+ } catch (e) {
87
+ log.error(`[Mastodon Comments Helper] 解析 ${file} Front-matter 失败:`, e);
88
+ continue;
89
+ }
90
+
91
+ // 过滤无需处理的文章
92
+ if (frontMatter.comments === false || frontMatter["toot-id"]) {
93
+ continue;
94
+ }
95
+
96
+ const postTitle = frontMatter.title || "无标题文章";
97
+ log.info(`[Mastodon Comments Helper] 为文章 "${postTitle}" 创建 Mastodon 嘟文...`);
98
+
99
+ // 构建嘟文内容
100
+ let tootContent = "";
101
+ if (mastodonConfig["TOOT_TEMPLATE"]) {
102
+ tootContent = mastodonConfig["TOOT_TEMPLATE"].replaceAll("{{postTitle}}", postTitle);
103
+ } else {
104
+ tootContent = `我发布了一篇文章《${postTitle}》,一起来评论吧~`;
105
+ }
106
+
107
+ try {
108
+ // 同步调用 Mastodon API
109
+ const apiUrl = `https://${mastodonConfig.MASTODON_DOMAIN}/api/v1/statuses`;
110
+ const requestOptions = {
111
+ json: {
112
+ status: tootContent,
113
+ visibility: "public"
114
+ },
115
+ headers: {
116
+ "Authorization": `Bearer ${mastodonConfig.MASTODON_TOKEN}`,
117
+ "Content-Type": "application/json"
118
+ },
119
+ timeout: 10000
120
+ };
121
+
122
+ // 调用封装的同步请求函数
123
+ const responseData = syncPostRequest(apiUrl, requestOptions);
124
+
125
+ // 处理响应
126
+ const tootId = responseData.id;
127
+ const tootURL = responseData.url;
128
+ log.info(`[Mastodon Comments Helper] 成功创建嘟文,ID: ${tootId}`);
129
+
130
+ // 插入 toot-id 到 date 上一行
131
+ const frontMatterLines = frontMatterBlock.trim().split("\n");
132
+ const newFrontMatterLines = [];
133
+ let dateLineIndex = -1;
134
+ for (let i = 0; i < frontMatterLines.length; i++) {
135
+ const line = frontMatterLines[i].trim();
136
+ if (line.startsWith("date:")) {
137
+ dateLineIndex = i;
138
+ break;
139
+ }
140
+ }
141
+
142
+ if (dateLineIndex !== -1) {
143
+ newFrontMatterLines.push(
144
+ ...frontMatterLines.slice(0, dateLineIndex),
145
+ `toot-id: "${tootId}"`,
146
+ ...frontMatterLines.slice(dateLineIndex)
147
+ );
148
+ } else {
149
+ newFrontMatterLines.push(...frontMatterLines, `toot-id: "${tootId}"`);
150
+ }
151
+
152
+ // 重新拼接并写入文件
153
+ const newFrontMatter = newFrontMatterLines.join("\n").trim() + "\n";
154
+ const newContent = `---\n${newFrontMatter}---\n${contentRest.join("---")}`;
155
+ fs.writeFileSync(filePath, newContent, "utf8");
156
+ log.info(`[Mastodon Comments Helper] 已将 toot-id 写入文件: ${file}`);
157
+ if (tootURL) {
158
+ log.info(`[Mastodon Comments Helper] 嘟文链接:${tootURL}`);
159
+ }
160
+ } catch (apiError) {
161
+ log.error("[Mastodon Comments Helper] 创建嘟文失败:", apiError.message);
162
+ }
163
+ }
164
+ });
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "hexo-next-mastodon-comments-helper",
3
+ "version": "1.0.0",
4
+ "description": "Auto add `toot-id` in post header.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "repository": "https://github.com/feiju12138/hexo-next-mastodon-comments-helper",
10
+ "keywords": [
11
+ "Hexo",
12
+ "NexT",
13
+ "mastodon-comments"
14
+ ],
15
+ "author": "feiju12138",
16
+ "license": "MIT",
17
+ "bugs": {
18
+ "url": "https://github.com/feiju12138/hexo-next-mastodon-comments-helper/issues"
19
+ },
20
+ "homepage": "https://github.com/feiju12138/hexo-next-mastodon-comments-helper#readme",
21
+ "dependencies": {
22
+ "js-yaml": "^4.1.1",
23
+ "sync-request": "^6.1.0"
24
+ }
25
+ }