学习如何编写规则和自定义脚本,扩展 MirageX 的页面处理能力
MirageX 提供两种扩展方式:
| 方式 | 适用场景 | 难度 | 说明 |
|---|---|---|---|
| JSON 规则 | 文本替换、隐藏元素、金额格式化 | ⭐ 简单 | 声明式 JSON 配置,无需编程基础 |
| 自定义脚本 | 复杂 DOM 操作、动态内容、多步骤处理 | ⭐⭐ 进阶 | JavaScript 代码,拥有完整的 DOM 操作能力 |
用「导入规则」如果你只需要:
用「导入脚本」如果你需要:
MirageX v2.2+ 内置了转账页面自动检测功能。当你打开转账页面并输入金额后,扩展会自动弹出确认框,确认后自动从虚拟余额中扣减。此功能无需手动配置,自动适配所有银行的转账页面。
两种方式都可以通过 popup 面板的「导入规则」和「导入脚本」按钮加载,也可以通过程序化方式批量部署。
规则是一个 JSON 数组,每条规则定义一个页面操作。基本结构:
[
{
"searchText": "要搜索的文本",
"searchType": "exact",
"newValue": "替换后的值",
"action": "replace",
"amountOnly": false
}
]
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
searchText | string | ✅ | 在页面中搜索的文本内容 |
searchType | string | ✅ | 搜索方式:exact(精确匹配)、prefix(前缀匹配)、contains(包含匹配) |
newValue | string | ✅ | 替换后的新值(hide 操作留空) |
action | string | ✅ | 操作类型:replace、hide |
amountOnly | boolean | ❌ | 是否只替换金额部分(保留前缀文字)。默认 false |
| searchType | 行为 | 示例 |
|---|---|---|
exact | 文本完全一致才匹配 | "Hello World" 只匹配 "Hello World" |
prefix | 文本以 searchText 开头 | "总计:" 匹配 "总计:1,234.56" |
contains | 文本包含 searchText | "订单号" 匹配 "您的订单号是 ABC123" |
将匹配到的文本节点替换为新值。
[
{
"searchText": "Hello World",
"searchType": "exact",
"newValue": "你好世界",
"action": "replace"
}
]
当页面文本为 "总计:1,234.56",你只想改数字不改前缀时使用。
[
{
"searchText": "总计:",
"searchType": "prefix",
"newValue": "9,999,999.00",
"action": "replace",
"amountOnly": true
}
]
这段规则的效果:找到以 "总计:" 开头的文本,只把其中的数字部分替换为 9,999,999.00,结果变为 "总计:9,999,999.00"。
隐藏包含匹配文本的整行(<tr> 或 <li>)。
[
{
"searchText": "测试数据行",
"searchType": "contains",
"newValue": "",
"action": "hide"
}
]
一个页面可以有多条规则,它们会全部执行:
[
{
"searchText": "商品单价:",
"searchType": "prefix",
"newValue": "1,299.00",
"action": "replace",
"amountOnly": true
},
{
"searchText": "订单总额:",
"searchType": "prefix",
"newValue": "1,299.00",
"action": "replace",
"amountOnly": true
},
{
"searchText": "优惠券抵扣",
"searchType": "contains",
"newValue": "",
"action": "hide"
},
{
"searchText": "物流状态:已发货",
"searchType": "exact",
"newValue": "物流状态:待发货",
"action": "replace"
}
]
my-rules.json),下次直接粘贴导入当规则无法满足需求时(如需要条件判断、循环、动态计算、操作多个元素等),可以编写 JavaScript 脚本。
*://*.example.com/*),符合规则的页面才会执行0 = 只执行一次;2000 = 每 2 秒重复执行| 特性 | 说明 |
|---|---|
| 运行位置 | 页面内容脚本环境(Content Script),与页面共享 DOM |
| 可用 API | document、window、console、setTimeout、setInterval、fetch 等 |
| ES 版本 | 支持 ES5 / ES6 语法(取决于浏览器版本) |
| 限制 | 无法访问 chrome.* API,无法跨域请求(受页面 CSP 限制) |
| 安全沙盒 | 脚本的 window 和 document 是真实页面环境 |
脚本中可以直接使用以下 DOM 操作方法:
// CSS 选择器
var el = document.querySelector('.price');
var els = document.querySelectorAll('table tr');
// 文本内容查找
function findByText(selector, text) {
var elements = document.querySelectorAll(selector);
for (var i = 0; i < elements.length; i++) {
if (elements[i].textContent.indexOf(text) !== -1) {
return elements[i];
}
}
return null;
}
// 修改元素文本
el.textContent = '新文本';
// 修改 HTML(注意安全)
el.innerHTML = '<span>新内容</span>';
// 修改 input 值
input.value = '新值';
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
// 单个样式
el.style.color = 'red';
el.style.fontWeight = 'bold';
el.style.display = 'none';
// 批量样式
Object.assign(el.style, {
color: '#ff0000',
backgroundColor: '#fff3cd',
fontWeight: 'bold',
fontSize: '16px'
});
// 添加/移除 CSS 类
el.classList.add('highlight');
el.classList.remove('hidden');
// 隐藏一行
function hideRow(text) {
var rows = document.querySelectorAll('tr');
for (var i = 0; i < rows.length; i++) {
if (rows[i].textContent.indexOf(text) !== -1) {
rows[i].style.display = 'none';
}
}
}
// 在表格末尾添加一行
var table = document.querySelector('table tbody') || document.querySelector('table');
if (table) {
var tr = document.createElement('tr');
tr.innerHTML = '<td>数据1</td><td>数据2</td><td>数据3</td>';
table.appendChild(tr);
}
// 在指定行之后插入
function insertRowAfter(targetText, html) {
var rows = document.querySelectorAll('tr');
for (var i = 0; i < rows.length; i++) {
if (rows[i].textContent.indexOf(targetText) !== -1) {
var newRow = document.createElement('tr');
newRow.innerHTML = html;
rows[i].parentNode.insertBefore(newRow, rows[i].nextSibling);
break;
}
}
}
function waitAndExecute(selector, callback, maxWait) {
maxWait = maxWait || 10000;
var start = Date.now();
function check() {
var el = document.querySelector(selector);
if (el) {
callback(el);
} else if (Date.now() - start < maxWait) {
setTimeout(check, 300);
}
}
check();
}
// 用法:等待 .price 元素出现后修改
waitAndExecute('.price', function(el) {
el.textContent = '1,299.00';
});
document.title = '我的自定义标题';
var h1 = document.querySelector('h1');
if (h1) h1.textContent = '欢迎来到我的网站';
var subtitle = document.querySelector('.subtitle');
if (subtitle) subtitle.textContent = '这是副标题';
var keyword = '重要';
var rows = document.querySelectorAll('table tbody tr');
for (var i = 0; i < rows.length; i++) {
if (rows[i].textContent.indexOf(keyword) !== -1) {
rows[i].style.backgroundColor = '#fff3cd';
rows[i].style.fontWeight = 'bold';
}
}
var table = document.querySelector('table tbody');
if (table) {
var totalRow = document.createElement('tr');
totalRow.innerHTML = '<td colspan="2"><strong>合计</strong></td><td><strong>3,897.50</strong></td>';
totalRow.style.backgroundColor = '#f0f0f0';
table.appendChild(totalRow);
}
var colIndex = 3;
var cells = document.querySelectorAll('table td:nth-child(' + (colIndex + 1) + '), table th:nth-child(' + (colIndex + 1) + ')');
for (var i = 0; i < cells.length; i++) {
cells[i].style.display = 'none';
}
function formatNumber(num) {
var parts = num.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
}
var priceEls = document.querySelectorAll('.price, .amount, .total');
for (var i = 0; i < priceEls.length; i++) {
var raw = priceEls[i].textContent.replace(/[,¥$¥]/g, '').trim();
var num = parseFloat(raw);
if (!isNaN(num)) {
priceEls[i].textContent = formatNumber(num);
}
}
var style = document.createElement('style');
style.textContent = '\
body { background-color: #1a1a2e !important; color: #e0e0e0 !important; }\
.card { background-color: #16213e !important; border-color: #0f3460 !important; }\
.header { background-color: #0f3460 !important; }\
a { color: #e94560 !important; }\
';
document.head.appendChild(style);
function fillInput(selector, value) {
var el = document.querySelector(selector);
if (!el) return;
el.value = value;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}
fillInput('#name', '张三');
fillInput('#email', 'zhangsan@example.com');
fillInput('#phone', '13800138000');
var observer = new MutationObserver(function() {
var items = document.querySelectorAll('.notification-item');
for (var i = 0; i < items.length; i++) {
if (items[i].textContent.indexOf('广告') !== -1) {
items[i].style.display = 'none';
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
F12 打开浏览器开发者工具Console 标签[MirageX] 开头,可以过滤查看console.log('[我的脚本] 开始执行');
var el = document.querySelector('.price');
console.log('[我的脚本] 找到元素:', el ? el.textContent : '未找到');
在 Console 中输入:
// 查看当前生效的规则
window.__AM && window.__AM.ruleEngine && window.__AM.ruleEngine.getActiveRules()
// 查看所有已缓存的规则
chrome.storage.local.get('am_rule_cache', function(d) { console.log(d); });
chrome.storage.local.remove('am_rule_cache', function() { console.log('缓存已清除'); });
fetch 和 XMLHttpRequest,但受浏览器同源策略限制,只能请求与当前页面同域的 API。跨域请求会被浏览器拦截。waitAndExecute 函数中等待元素出现),或将执行间隔设为非 0 值让脚本反复执行。