为hexo博客添加隐藏ai文本的功能

昨天刚刚斗完赛博蛐蛐,把文章上传到博客。这时我发现我的博客中,文章主要内容为ai生成的博客竟然已达6篇,就想到一个功能,那就是为hexo博客添加隐藏ai文本的功能。

我的初步构想是:在用户第一次进入博客是,弹窗询问:你是否能够接受AI-generated文本的展示?

如果用户选择是,则展示带有AI-gen标签的文章,用户选择否,则隐藏带有AI-gen标签的文章。而且此过程由cookie记录。为了简化代码量,可以提示:如需更改选项,只需清除cookie即可。

这样,能接受ai-gen的人,可以看到博客里一些自娱自乐性质的文章,以及我还读高三时创作的虾塔系列。陈家明读完(他显然难以读完这些又臭又长的后现代先锋狗屎)后告诉我,搭配上ai生成让这些奇异搞笑的故事具有类似迷幻剂的效果。

注:本博客是不存在100% ai生成的文章的,文章前的序都是我自己手写的,不过如果文章主体内容为ai生成,就会被我归入ai-gen,当然代码类内容除外。

不能接受ai-gen的人,将会获得一个干净的主页,可以看到100%我自行创作的文章,也就免去了被ai污染的烦恼。

说干就干,虽然这种小事交给美国豆包Gemini足以,但我还是要大炮打蚊子,搬出肥波来,只是为了满足一下我的好奇心而已。

撰写prompt,询问肥波:

1
2
3
4
5
6
7
我想让hexo博客实现如下功能:

在用户第一次进入博客是,弹窗询问:你是否能够接受AI-generated文本的展示?

用户选择是,则展示带有AI-gen标签的文章,用户选择否,则隐藏带有AI-gen标签的文章。而且此过程由cookie记录。为了简化代码量,可以提示:如需更改选项,只需清除cookie即可。

其他信息:hexo版本7.3.0 还安装了next主题版本8.12.2

肥波很快给出了一套方案,这也是我的第一套方案

方案1:全部隐藏

全部隐藏是包括主页/归档…等等其他页面都不能看到ai生成的文章,即使你用直链访问,也会看到空白界面。

肥波的方案如下:

思路:构建时用 Nunjucks 注入点把所有带 AI-gen 标签的文章路径输出成一个 JS 数组,前端脚本根据 cookie 决定是否弹窗、是否隐藏这些文章(首页/归档页的条目 + 文章页本身)。

1. 给文章打标签

在需要标记的文章 front-matter 中:

1
2
3
4
5
---
title: 某篇 AI 生成的文章
tags:
- AI-gen
---

2. 开启 NexT 自定义注入点

编辑 NexT 配置文件(_config.next.yml 或主题 _config.yml):

1
2
custom_file_path:
bodyEnd: source/_data/body-end.njk

3. 新建 source/_data/body-end.njk

1
2
3
4
5
6
7
8
9
10
11
12
13
{# 输出所有带 AI-gen 标签的文章路径 #}
<script>
window.AI_GEN_POSTS = [
{%- for post in site.posts.toArray() -%}
{%- for tag in post.tags.toArray() -%}
{%- if tag.name == 'AI-gen' -%}
"{{ url_for(post.path) }}",
{%- endif -%}
{%- endfor -%}
{%- endfor -%}
];
</script>
<script src="{{ url_for('/js/ai-gen-filter.js') }}"></script>

4. 新建 source/js/ai-gen-filter.js

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
(function () {
var COOKIE_NAME = 'ai_gen_consent';

/* ---------- Cookie 工具 ---------- */
function getCookie(name) {
var match = document.cookie.match(new RegExp('(?:^|;\\s*)' + name + '=([^;]*)'));
return match ? decodeURIComponent(match[1]) : null;
}
function setCookie(name, value, days) {
document.cookie = name + '=' + encodeURIComponent(value) +
'; path=/; max-age=' + days * 86400;
}

/* ---------- 路径规范化 ---------- */
function normalize(href) {
try {
return new URL(href, location.origin).pathname;
} catch (e) {
return href;
}
}

var aiPaths = (window.AI_GEN_POSTS || []).map(normalize);
if (!aiPaths.length) return;

/* ---------- 隐藏 AI-gen 文章 ---------- */
function hideAIGenPosts() {
// 1) 若当前正处于 AI-gen 文章页,隐藏正文并提示
if (aiPaths.indexOf(normalize(location.pathname)) !== -1) {
var post = document.querySelector('.post-block') || document.querySelector('.main-inner');
if (post) {
post.innerHTML =
'<div style="text-align:center;padding:80px 20px;color:#888;">' +
'<p style="font-size:18px;">本文包含 AI-generated 内容,已根据您的选择隐藏。</p>' +
'<p>如需更改选项,清除本站 Cookie 后刷新即可。</p>' +
'</div>';
}
}
// 2) 隐藏首页 / 归档页中的对应条目
document.querySelectorAll('a[href]').forEach(function (a) {
if (aiPaths.indexOf(normalize(a.getAttribute('href'))) !== -1) {
var block = a.closest('.post-block, article');
if (block) block.style.display = 'none';
}
});
}

/* ---------- 弹窗 ---------- */
function showModal() {
var overlay = document.createElement('div');
overlay.id = 'ai-gen-modal';
overlay.style.cssText =
'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.55);' +
'display:flex;align-items:center;justify-content:center;';
overlay.innerHTML =
'<div style="background:#fff;max-width:420px;width:90%;border-radius:8px;' +
'padding:28px 24px;text-align:center;box-shadow:0 8px 30px rgba(0,0,0,.25);">' +
'<h3 style="margin:0 0 12px;font-size:18px;color:#333;">内容展示确认</h3>' +
'<p style="margin:0 0 8px;color:#555;line-height:1.7;">' +
'你是否能够接受 AI-generated 文本的展示?</p>' +
'<p style="margin:0 0 20px;color:#999;font-size:13px;">' +
'提示:如需更改选项,清除本站 Cookie 即可。</p>' +
'<button id="ai-gen-yes" style="margin:0 8px;padding:8px 26px;border:none;' +
'border-radius:4px;background:#409eff;color:#fff;cursor:pointer;">是</button>' +
'<button id="ai-gen-no" style="margin:0 8px;padding:8px 26px;border:1px solid #ccc;' +
'border-radius:4px;background:#fff;color:#555;cursor:pointer;">否</button>' +
'</div>';
document.body.appendChild(overlay);

document.getElementById('ai-gen-yes').onclick = function () {
setCookie(COOKIE_NAME, 'yes', 365);
overlay.remove();
};
document.getElementById('ai-gen-no').onclick = function () {
setCookie(COOKIE_NAME, 'no', 365);
overlay.remove();
hideAIGenPosts();
};
}

/* ---------- 主流程 ---------- */
function main() {
var consent = getCookie(COOKIE_NAME);
if (consent === 'no') {
hideAIGenPosts();
} else if (!consent) {
showModal();
}
// consent === 'yes' 则什么都不做,正常展示
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
main();
}

// 若开启了 NexT 的 pjax,切换页面后重新执行过滤
document.addEventListener('pjax:success', function () {
if (getCookie(COOKIE_NAME) === 'no') hideAIGenPosts();
});
})();

5. 重新生成

1
hexo clean && hexo g && hexo s

说明

  • cookie 有效期 365 天,键名为 ai_gen_consent,值为 yes / no;用户清除 cookie 后再次访问会重新弹窗。
  • 弹窗只在没有 cookie(即首次访问或清除后)时出现。
  • 选择”否”时:文章列表(首页、归档、标签页等)中带 AI-gen 标签的条目被隐藏;若用户通过直链进入 AI-gen 文章页,正文会被替换为提示信息。
  • 文章列表由构建时生成的 window.AI_GEN_POSTS 数组驱动,无需在前端解析标签,兼容 NexT 各种列表样式。
  • 如果你开启了 NexT 的 pjax: true,脚本已通过监听 pjax:success 做了兼容。

方案2:只在主页隐藏

我最终采用了这个方案

只需调整 hideAIGenPosts 的作用范围:仅在首页(含首页分页)生效,归档页、标签页、文章页均不做处理。

1. 修改 source/_data/body-end.njk

增加输出站点根路径,方便前端判断当前是否为首页:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{# 输出所有带 AI-gen 标签的文章路径 #}
<script>
window.AI_GEN_POSTS = [
{%- for post in site.posts.toArray() -%}
{%- for tag in post.tags.toArray() -%}
{%- if tag.name == 'AI-gen' -%}
"{{ url_for(post.path) }}",
{%- endif -%}
{%- endfor -%}
{%- endfor -%}
];
window.SITE_ROOT = "{{ config.root }}";
</script>
<script src="{{ url_for('/js/ai-gen-filter.js') }}"></script>

2. 替换 source/js/ai-gen-filter.js

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
(function () {
var COOKIE_NAME = 'ai_gen_consent';

/* ---------- Cookie 工具 ---------- */
function getCookie(name) {
var match = document.cookie.match(new RegExp('(?:^|;\\s*)' + name + '=([^;]*)'));
return match ? decodeURIComponent(match[1]) : null;
}
function setCookie(name, value, days) {
document.cookie = name + '=' + encodeURIComponent(value) +
'; path=/; max-age=' + days * 86400;
}

/* ---------- 路径规范化 ---------- */
function normalize(href) {
try {
return new URL(href, location.origin).pathname;
} catch (e) {
return href;
}
}

var aiPaths = (window.AI_GEN_POSTS || []).map(normalize);
if (!aiPaths.length) return;

/* ---------- 判断当前是否为首页(含首页分页 /page/N/) ---------- */
function isHomePage() {
var root = window.SITE_ROOT || '/';
var path = location.pathname;
// 首页:/ 或 /page/2/ 这类首页分页
return path === root ||
new RegExp('^' + root.replace(/\//g, '\\/') + 'page\\/\\d+\\/?$').test(path);
}

/* ---------- 仅在首页隐藏 AI-gen 文章条目 ---------- */
function hideAIGenPostsOnHome() {
if (!isHomePage()) return; // 归档页、标签页、文章页一律不处理

document.querySelectorAll('a[href]').forEach(function (a) {
if (aiPaths.indexOf(normalize(a.getAttribute('href'))) !== -1) {
var block = a.closest('.post-block, article');
if (block) block.style.display = 'none';
}
});
}

/* ---------- 弹窗 ---------- */
function showModal() {
var overlay = document.createElement('div');
overlay.id = 'ai-gen-modal';
overlay.style.cssText =
'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.55);' +
'display:flex;align-items:center;justify-content:center;';
overlay.innerHTML =
'<div style="background:#fff;max-width:420px;width:90%;border-radius:8px;' +
'padding:28px 24px;text-align:center;box-shadow:0 8px 30px rgba(0,0,0,.25);">' +
'<h3 style="margin:0 0 12px;font-size:18px;color:#333;">内容展示确认</h3>' +
'<p style="margin:0 0 8px;color:#555;line-height:1.7;">' +
'你是否能够接受 AI-generated 文本的展示?</p>' +
'<p style="margin:0 0 8px;color:#999;font-size:13px;">' +
'选择"否"仅会在首页隐藏相关文章,归档页与直链访问不受影响。</p>' +
'<p style="margin:0 0 20px;color:#999;font-size:13px;">' +
'提示:如需更改选项,清除本站 Cookie 即可。</p>' +
'<button id="ai-gen-yes" style="margin:0 8px;padding:8px 26px;border:none;' +
'border-radius:4px;background:#409eff;color:#fff;cursor:pointer;">是</button>' +
'<button id="ai-gen-no" style="margin:0 8px;padding:8px 26px;border:1px solid #ccc;' +
'border-radius:4px;background:#fff;color:#555;cursor:pointer;">否</button>' +
'</div>';
document.body.appendChild(overlay);

document.getElementById('ai-gen-yes').onclick = function () {
setCookie(COOKIE_NAME, 'yes', 365);
overlay.remove();
};
document.getElementById('ai-gen-no').onclick = function () {
setCookie(COOKIE_NAME, 'no', 365);
overlay.remove();
hideAIGenPostsOnHome();
};
}

/* ---------- 主流程 ---------- */
function main() {
var consent = getCookie(COOKIE_NAME);
if (consent === 'no') {
hideAIGenPostsOnHome();
} else if (!consent) {
showModal();
}
// consent === 'yes' 则正常展示,无任何处理
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
main();
}

// 兼容 NexT pjax:页面切换后重新判断
document.addEventListener('pjax:success', function () {
if (getCookie(COOKIE_NAME) === 'no') hideAIGenPostsOnHome();
});
})();

3. 重新生成

1
hexo clean && hexo g && hexo s

相比上一版的改动点

项目 上一版 本版
首页 隐藏 AI-gen 条目 隐藏 AI-gen 条目(不变)
归档页 / 标签页 隐藏 正常展示
直接访问文章链接 正文被替换为提示 正常展示全文

关键实现是新增的 isHomePage():通过对比 location.pathname 与站点根路径(以及 /page/N/ 形式的首页分页),确保过滤逻辑只在首页触发。归档页(/archives/...)、标签页、分类页和文章详情页都不会匹配,因此完全不受影响。

注意:如果你的博客部署在子目录(例如 https://xxx.github.io/blog/),请确保 Hexo _config.yml 中的 root: /blog/ 配置正确,isHomePage() 依赖它来判断首页路径。


这样就大功告成了。

---------文章结束啦~有什么想评论的吗?---------
欢迎关注我的其它发布渠道