自动替换网页文本,支持正则、快捷键控制、导入导出规则。仅在设置中指定的网站生效
// ==UserScript==
// @name 通用文本替换工具
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 自动替换网页文本,支持正则、快捷键控制、导入导出规则。仅在设置中指定的网站生效
// @author 233YUZI
// @match *://www.shitouxs.com/*
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(function () {
'use strict'
const shortcutKey = 'm'
const shortcutCtrl = true
const panelId = 'replace-text-panel'
const domain = location.hostname
let allRules = GM_getValue('replace_rules', {})
function parseRuleFrom(raw) {
const regMatch = raw.match(/^\/(.+)\/([gimsuy]*)$/)
if (regMatch) {
try {
return { type: 'regex', value: new RegExp(regMatch[1], regMatch[2]) }
} catch {
alert('无效的正则表达式')
return null
}
} else {
return { type: 'text', value: raw }
}
}
function getCurrentDomainRules() {
return allRules[domain] || []
}
function saveRules(domainKey, rules) {
allRules[domainKey] = rules
GM_setValue('replace_rules', allRules)
}
function addRule(toAll, from, to) {
if (!from) return
const newRule = { from, to }
if (toAll) {
for (const d of Object.keys(allRules)) {
allRules[d].push(newRule)
}
} else {
if (!allRules[domain]) allRules[domain] = []
allRules[domain].push(newRule)
}
GM_setValue('replace_rules', allRules)
alert('添加成功,刷新页面生效')
}
function deleteRule(index) {
if (!allRules[domain]) return
allRules[domain].splice(index, 1)
GM_setValue('replace_rules', allRules)
alert('删除成功,刷新页面生效')
}
function replaceText(rules) {
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false)
const textNodes = []
while (walker.nextNode()) textNodes.push(walker.currentNode)
for (const node of textNodes) {
let text = node.nodeValue
for (const { from, to } of rules) {
const parsed = parseRuleFrom(from)
if (!parsed) continue
text = parsed.type === 'regex' ? text.replace(parsed.value, to) : text.split(parsed.value).join(to)
}
node.nodeValue = text
}
}
function createPanel() {
if (document.getElementById(panelId)) return
const panel = document.createElement('div')
panel.id = panelId
panel.style = `
position: fixed;
top: 100px;
right: 100px;
z-index: 9999;
background: white;
border: 1px solid black;
padding: 10px;
font-size: 14px;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
max-width: 350px;
`
panel.innerHTML = `
<div>
<strong>当前域名:${domain}</strong><br><br>
<input id="input-from" placeholder="要替换的文本或正则" style="width: 100%; margin-bottom: 5px;"><br>
<input id="input-to" placeholder="替换后的文本" style="width: 100%; margin-bottom: 5px;"><br>
<button id="btn-current">仅当前网站生效</button>
<button id="btn-all">所有网站生效</button>
<hr>
<select id="rule-list" size="5" style="width: 100%; margin-top: 5px;"></select><br>
<button id="btn-delete" disabled>删除选中规则</button>
<hr>
<button id="btn-export">导出规则</button>
<button id="btn-import">导入规则</button><br>
<textarea id="import-text" placeholder="在此粘贴导入JSON" style="width: 100%; height: 60px; margin-top: 5px;"></textarea><br>
<button id="btn-close" style="float:right;">关闭</button>
</div>
`
document.body.appendChild(panel)
const ruleList = document.getElementById('rule-list')
const deleteBtn = document.getElementById('btn-delete')
function refreshList() {
ruleList.innerHTML = ''
const rules = getCurrentDomainRules()
rules.forEach((r, i) => {
const option = document.createElement('option')
option.value = i
option.textContent = `替换:${r.from} → ${r.to}`
ruleList.appendChild(option)
})
deleteBtn.disabled = true
}
ruleList.addEventListener('change', () => {
deleteBtn.disabled = ruleList.selectedIndex === -1
})
document.getElementById('btn-current').onclick = () => {
const from = document.getElementById('input-from').value.trim()
const to = document.getElementById('input-to').value
if (parseRuleFrom(from)) {
addRule(false, from, to)
refreshList()
}
}
document.getElementById('btn-all').onclick = () => {
const from = document.getElementById('input-from').value.trim()
const to = document.getElementById('input-to').value
if (parseRuleFrom(from)) {
addRule(true, from, to)
refreshList()
}
}
deleteBtn.onclick = () => {
const index = ruleList.selectedIndex
if (index > -1) {
deleteRule(index)
refreshList()
}
}
document.getElementById('btn-close').onclick = () => panel.remove()
document.getElementById('btn-export').onclick = () => {
const json = JSON.stringify(allRules, null, 2)
navigator.clipboard.writeText(json)
alert('规则已复制到剪贴板')
}
document.getElementById('btn-import').onclick = () => {
const json = document.getElementById('import-text').value
try {
const imported = JSON.parse(json)
for (const key in imported) {
if (!allRules[key]) allRules[key] = []
allRules[key].push(...imported[key])
}
GM_setValue('replace_rules', allRules)
alert('导入成功,刷新页面后生效')
refreshList()
} catch {
alert('导入失败:格式错误')
}
}
refreshList()
}
window.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === shortcutKey && e.ctrlKey === shortcutCtrl) {
const panel = document.getElementById(panelId)
if (panel) panel.remove()
else createPanel()
}
})
replaceText(getCurrentDomainRules())
})()