昨天刚刚斗完赛博蛐蛐,把文章上传到博客。这时我发现我的博客中,文章主要内容为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() 依赖它来判断首页路径。
这样就大功告成了。