

























我之前分享过一个书签脚本:
随着最近接触 Halo 2.x 等现代博客系统越来越多,我发现这个老脚本 "失灵" 了。问题根源不再是简单的 "找不到输入框",而是前端框架的响应式绑定机制——你改了 value,框架并不知道,点击提交时数据就被重置了。
为了解决这个问题,在跟 AI 死磕良久,对代码进行了数次重构与逻辑审计之后,最终打磨出了这套全平台通用版。
走过路过,瞧一瞧看一看了嘿 ~

以前的填充脚本在 Typecho 上表现完美,但在 Halo 2.x 或一些现代 Vue/React 主题上,填完后点击提交,信息会瞬间消失,或者啥也没填充上。
这并不是因为找不到元素,而是 "数据绑定" 断了。 现代前端框架(如 Vue 3、React)不再通过读取 DOM 的 value 来获取数据,而是通过监听 input 事件来同步内部状态 。如果你的脚本只是修改了 value 而没有触发相应的事件,框架的内部变量依然是空的,提交时自然会 "穿帮"。
Vue 的 v-model 指令本质上是 :value 绑定与 @input 事件监听器的语法糖 。当用户输入时,input 事件被触发,框架将 event.target.value 同步到内部状态;反之,当状态变化时,框架更新 DOM 的 value。但如果通过脚本直接修改 value 而不派发事件,框架的监听器永远不会被触发,导致状态与 DOM 脱节。
特征类型 | 代表程序 | 核心挑战 | 脚本应对方案 |
|---|---|---|---|
传统 SSR | Typecho / WordPress | DOM 结构固定,无特殊逻辑 | 标准 ID 匹配 + 快速填充 |
响应式架构 | Halo 2.x / Vue 主题 | 填入 value 但无法触发生命周期 | 派发冒泡事件流 (input/change/blur) |
异步加载 | Hexo (Valine/Waline) | 脚本运行瞬间评论框还没渲染 | MutationObserver 异步捕获 |
Web 组件 | 部分第三方评论插件 | 存在 Shadow DOM 封装隔离 | 深度递归穿透搜索 |
代码中不仅考虑了选择器的覆盖,更核心的是解决了数据同步和异步渲染的问题。
事件驱动同步:通过触发 input、change、blur 三重事件,确保 Vue/React 等框架的响应式系统能够捕获到数据变化 。
MutationObserver 异步捕获:针对评论框异步注入的场景(如 Valine、Waline),监听 DOM 变化并在元素出现时自动填充。
Shadow DOM 穿透:递归遍历 shadowRoot,兼容基于 Web Components 的第三方评论系统。
隐藏域规避:过滤 type="hidden" 的输入框,避免误触反垃圾 "蜜罐" 机制。
单次执行去重:依赖执行周期内的状态判断,避免对同一元素重复填充。
/*
* 全平台评论自动填充脚本 (通用增强版 V2.0)
* 核心:响应式事件同步 + MutationObserver 异步捕获 + 隐藏域规避
*/
(function() {
// --- [1] 个人信息配置区 ---
var config = {
data: {
n: '吴蛋蛋', // 你的昵称
e: '[email protected]', // 你的邮箱
w: 'https://wuqishi.com' // 你的网址(末尾不要留空格)
},
// --- [2] 属性选择器规则库 ---
// 涵盖 Typecho, Halo, WP, Z-Blog 等主流主题特征
rules: {
n: ['#author', '#inpName', 'input[name="author"]', 'input[placeholder*="昵称"]'],
e: ['#mail', '#email', 'input[name="email"]', 'input[placeholder*="邮箱"]'],
w: ['input[type="url"]', '#url', 'input[name="url"]', 'input[placeholder*="网址"]']
}
};
var filledCount = 0;
var observer = null;
/**
* 执行填充逻辑
* 返回 boolean:是否成功填充至少一个字段
*/
function executeFill() {
var foundThisRound = 0;
for (var k in config.rules) {
var selectors = config.rules[k];
for (var i = 0; i < selectors.length; i++) {
var el = deepQuery(document, selectors[i]);
// 核心判定:排除 hidden 蜜罐,确保是可见输入框
if (el && el.type !== 'hidden' && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA')) {
el.value = config.data[k];
// 派发事件:bubbles 保证在 DOM 树中冒泡
// composed: true 允许事件从 Shadow DOM 内部向外冒泡(若元素位于 shadowRoot 内)
var evs = ['input', 'change', 'blur'];
for (var j = 0; j < evs.length; j++) {
el.dispatchEvent(new Event(evs[j], { bubbles: true, composed: true }));
}
filledCount++;
foundThisRound++;
break; // 该类目匹配成功即跳出
}
}
}
if (foundThisRound > 0) {
showFeedback(filledCount);
// 填充成功后停止观察,释放性能
if (observer) { observer.disconnect(); observer = null; }
return true;
}
return false;
}
/**
* 递归穿透 Shadow DOM
* 普通 querySelector 无法进入 shadowRoot,必须手动下潜
*/
function deepQuery(root, sel) {
var el = root.querySelector(sel);
if (el) return el;
var all = root.querySelectorAll('*');
for (var i = 0; i < all.length; i++) {
if (all[i].shadowRoot) {
el = deepQuery(all[i].shadowRoot, sel);
if (el) return el;
}
}
return null;
}
/**
* 初始化:立即执行 + MutationObserver 兜底
*/
function init() {
// 立即执行一次
if (executeFill()) return;
// 若未找到,监听 DOM 变化(应对异步注入的评论框)
observer = new MutationObserver(function() {
if (executeFill() && observer) {
observer.disconnect();
observer = null;
}
});
observer.observe(document.body, { childList: true, subtree: true });
// 10 秒后停止监听,避免长期性能损耗
setTimeout(function() {
if (observer) { observer.disconnect(); observer = null; }
}, 10000);
}
/**
* UI 反馈提示
*/
function showFeedback(count) {
var msg = document.getElementById('h-msg') || document.createElement('div');
msg.id = 'h-msg';
msg.textContent = '✅ 已填充 ' + count + ' 项字段';
msg.style.cssText = 'position:fixed;top:20px;right:20px;background:#4CAF50;color:white;padding:12px 24px;border-radius:4px;z-index:999999;font-family:sans-serif;box-shadow:0 2px 10px rgba(0,0,0,0.2);animation:h-fade 3s ease';
if (!msg.parentNode) document.body.appendChild(msg);
setTimeout(function() { if(msg.parentNode) msg.parentNode.removeChild(msg); }, 3000);
if (!document.getElementById('h-style')) {
var style = document.createElement('style');
style.id = 'h-style';
style.textContent = '@keyframes h-fade{0%,100%{opacity:0;transform:translateY(-10px)}15%,85%{opacity:1;transform:translateY(0)}}';
document.head.appendChild(style);
}
}
// 启动
init();
})();
将上述逻辑压缩成标准 ES5 语法,确保最大兼容性。
在浏览器书签栏右键 → 添加网页 / 书签。
名称:填入 "自动填表" 或 "一键评论"。
网址 (URL):完整复制粘贴下方这一行压缩代码:
javascript:(function(){var d={n:'吴蛋蛋',e:'[email protected]',w:'https://wuqishi.com'},r={n:['#author','#inpName','input[name="author"]','input[placeholder*="昵称"]'],e:['#mail','#email','input[name="email"]','input[placeholder*="邮箱"]'],w:['input[type="url"]','#url','input[name="url"]','input[placeholder*="网址"]']},f=0,o=null;function x(){var a=0;for(var k in r){var s=r[k];for(var i=0;i<s.length;i++){var el=q(document,s[i]);if(el&&el.type!=='hidden'&&(el.tagName==='INPUT'||el.tagName==='TEXTAREA')){el.value=d[k];var v=['input','change','blur'];for(var j=0;j<v.length;j++)el.dispatchEvent(new Event(v[j],{bubbles:true,composed:true}));f++;a++;break}}}if(a>0){y(f);if(o){o.disconnect();o=null}return true}return false}function q(root,sel){var e=root.querySelector(sel);if(e)return e;var a=root.querySelectorAll('*');for(var i=0;i<a.length;i++){if(a[i].shadowRoot){e=q(a[i].shadowRoot,sel);if(e)return e}}return null}function init(){if(x())return;o=new MutationObserver(function(){if(x()&&o){o.disconnect();o=null}});o.observe(document.body,{childList:true,subtree:true});setTimeout(function(){if(o){o.disconnect();o=null}},10000)}function y(c){var m=document.getElementById('h-msg')||document.createElement('div');m.id='h-msg';m.textContent='✅ 已填充 '+c+' 项';m.style.cssText='position:fixed;top:20px;right:20px;background:#4CAF50;color:white;padding:12px 24px;border-radius:4px;z-index:999999;font-family:sans-serif;box-shadow:0 2px 10px rgba(0,0,0,0.2);animation:h-fade 3s ease';if(!m.parentNode)document.body.appendChild(m);setTimeout(function(){if(m.parentNode)m.parentNode.removeChild(m)},3000);if(!document.getElementById('h-style')){var s=document.createElement('style');s.id='h-style';s.textContent='@keyframes h-fade{0%,100%{opacity:0;transform:translateY(-10px)}15%,85%{opacity:1;transform:translateY(0)}}';document.head.appendChild(s)}}init()})();
你可以保存多个书签,每个对应不同身份:
书签名称 | 配置示例 | 使用场景 |
|---|---|---|
一键评论 - 博主 | n:'吴蛋蛋', e:'[email protected]'... | 日常友链互动 |
一键评论 - 马甲 | n:'匿名网友', e:'[email protected]'... | 测试或隐私场景 |
只需修改 config.data 里的内容,即可实现 "切号" 自由。
很多老脚本只改了 value,没触发事件。Vue/React 等框架监听 input 事件来同步状态到内部数据层 。本脚本派发 input、change、blur 三重事件,确保框架状态树与 DOM 同步。
这个参数只在事件从 Shadow DOM 内部向外冒泡时生效。如果目标网站没有使用 Shadow DOM,它是无害的空操作;如果使用了,它确保事件能跨出封装边界被父级监听。
压缩版中包含了 MutationObserver,如果首次执行时评论框还没加载(常见于 Valine、Gitalk),它会监听 DOM 变化并在元素出现时自动触发填充,最多观察 10 秒后自动释放。
打开浏览器开发者工具(F12)→ Console 面板,点击书签后若看到绿色提示框但未生效,可检查:
输入框的 id 或 name 属性是否匹配规则库
目标站点是否使用了 closed Shadow DOM(无法穿透)
是否处于跨域 iframe 内(无法操作)
技术方案的诚实边界,是建立互相信任的基础。以下场景目前无法解决:
跨域 iframe:如 Disqus、部分版本的畅言,将评论框置于与主站不同源的 iframe 中,受浏览器同源策略限制,任何脚本都无法穿透。
Closed Shadow DOM:若第三方组件使用 attachShadow({mode: 'closed'}),element.shadowRoot 返回 null,递归穿透失效。
严格 CSP 限制:若目标站点配置了禁止内联脚本执行的 Content-Security-Policy,书签脚本会被浏览器拦截,无绕过方案。
Q: 这个脚本安全吗?会不会泄露我的信息?
A: 脚本完全在本地浏览器执行,不发送任何网络请求,你的个人信息不会离开本机。
Q: 为什么不用浏览器自带的自动填充功能?
A: 浏览器自动填充依赖表单识别,对中文博客系统的兼容性较差,且无法处理 Shadow DOM 和异步注入场景。
Q: 脚本支持移动端吗?
A: 移动端浏览器(如 Safari、Chrome)同样支持书签脚本,但操作方式略有不同,需通过 "添加到主屏幕" 或书签栏调用。
Q: 如何为我的自定义主题添加支持?
A: 在 config.rules 中追加对应的选择器即可。例如你的主题昵称框 ID 是 nickName,则添加 '#nickName' 到 n 数组中。
以上,欢迎给我留评论啊 ~
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。