昨天刚刚斗完赛博蛐蛐,把文章上传到博客。这时我发现我的博客中,文章主要内容为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';
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;
function hideAIGenPosts() { 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>'; } } 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(); } }
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', main); } else { main(); }
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';
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;
function isHomePage() { var root = window.SITE_ROOT || '/'; var path = location.pathname; return path === root || new RegExp('^' + root.replace(/\//g, '\\/') + 'page\\/\\d+\\/?$').test(path); }
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(); } }
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', main); } else { main(); }
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() 依赖它来判断首页路径。
这样就大功告成了。