// ==UserScript==
// @name Google搜索结果将吴语维基改为简中
// @namespace ZFLEO
// @version 1.5.3
// @description 改写链接与可见域名;摘要保留高亮
// @match *://google.com/*
// @match *://*.google.com/*
// @match *://*.google.com.*/*
// @match *://*.google.co.*/*
// @match *://*.google.*/*
// @run-at document-start
// @grant GM_xmlhttpRequest
// @connect zh.wikipedia.org
// @license MIT
// ==/UserScript==
(function () {
const WUU = ['wuu.wikipedia.org','wuu.m.wikipedia.org','m.wuu.wikipedia.org'];
const MAP = new Map([
['wuu.wikipedia.org','zh.wikipedia.org'],
['wuu.m.wikipedia.org','zh.m.wikipedia.org'],
['m.wuu.wikipedia.org','m.zh.wikipedia.org'],
]);
const USE_API_SUMMARY = true;
// 徽标样式,防止被容器 RTL/transform 影响
const style = document.createElement('style');
style.textContent = `.wuu2zh-badge{margin-left:6px;padding:1px 4px;border:1px solid #ccc;border-radius:3px;font-size:12px;color:#555;display:inline-block;direction:ltr;unicode-bidi:isolate;transform:none;writing-mode:horizontal-tb}`;
document.documentElement.appendChild(style);
const isGoogle = u => /\.google\./.test(u.hostname);
function getQuery(){
try { return decodeURIComponent(new URL(location.href).searchParams.get('q') || '').trim(); }
catch { return ''; }
}
function escapeHTML(s){ return s.replace(/[&<>"']/g, c=>({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
function escapeReg(s){ return s.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'); }
// 依据当前查询词恢复 Google 的 <em> 高亮
function highlightHTML(text){
const q = getQuery();
if (!q) return escapeHTML(text);
const keys = q.includes(' ') ? q.split(/\s+/).filter(t=>t.length>1) : [q];
let html = escapeHTML(text);
for (const k of keys){
const re = new RegExp(escapeReg(k), 'gi');
html = html.replace(re, m=>`<em>${m}</em>`);
}
return html;
}
function unwrapGoogle(href){
try{
const u = new URL(href);
if (isGoogle(u) && (u.pathname==='/url' || u.pathname.startsWith('/imgres'))) {
return u.searchParams.get('q') || u.searchParams.get('url') || u.searchParams.get('imgurl') || href;
}
return href;
}catch{return href}
}
function rewriteURL(target){
try{
const t = new URL(target);
if (!WUU.includes(t.hostname)) return null;
t.hostname = MAP.get(t.hostname) || t.hostname;
if (!t.searchParams.has('variant')) t.searchParams.set('variant','zh-hans');
return t.toString();
}catch{return null}
}
function findResultCard(a){
return a.closest('div[data-hveid], div.g, div.MjjYud') || a.closest('div');
}
function patchVisible(card, oldHost, newHost){
if (!card) return;
const cite = card.querySelector('cite');
if (cite && cite.textContent.includes(oldHost)){
cite.textContent = cite.textContent.replaceAll(oldHost, newHost);
}
}
function fetchZhSummary(title, cb){
const url = `https://zh.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(title)}?redirect=true`;
GM_xmlhttpRequest({
method: 'GET', url,
onload: r=>{ try{ cb(JSON.parse(r.responseText).extract || ''); }catch{ cb('') } },
onerror: ()=>cb('')
});
}
function replaceSnippet(card, title){
if (!USE_API_SUMMARY) return;
const node = card.querySelector('div.VwiC3b, div[data-sncf], span.aCOpRe, div.Uroaid');
if (!node) return;
fetchZhSummary(title, txt=>{
if (!txt) return;
node.innerHTML = highlightHTML(txt); // 关键:用 innerHTML 写回并恢复 <em>
});
}
function handleLink(a){
const real = unwrapGoogle(a.href);
const rew = rewriteURL(real);
if (!rew) return;
a.href = rew;
a.removeAttribute('ping');
a.removeAttribute('onmousedown');
const uOld = new URL(real);
const uNew = new URL(rew);
const card = findResultCard(a);
patchVisible(card, uOld.hostname, uNew.hostname);
const m = uNew.pathname.match(/\/wiki\/(.+)$/);
if (m) replaceSnippet(card, decodeURIComponent(m[1]));
}
function sweep(root){ (root || document).querySelectorAll('a[href]').forEach(handleLink); }
function enable(){ sweep(document); }
const mo = new MutationObserver(ms=>{
for (const m of ms) for (const n of m.addedNodes) if (n.nodeType===1) sweep(n);
});
function hookHistory(){
['pushState','replaceState'].forEach(k=>{
const orig = history[k];
history[k] = function(){ const r = orig.apply(this, arguments); queueMicrotask(enable); return r; };
});
addEventListener('popstate', enable, true);
}
function intercept(e){
const a = e.target && e.target.closest && e.target.closest('a[href]');
if (!a) return;
const real = unwrapGoogle(a.href);
const rew = rewriteURL(real);
if (rew){
e.preventDefault();
e.stopPropagation();
open(rew, (e.type==='auxclick' && e.button===1) ? '_blank' : '_self');
}
}
hookHistory();
if (document.readyState==='loading') {
document.addEventListener('DOMContentLoaded', ()=>{ enable(); mo.observe(document, {childList:true, subtree:true}); }, {once:true});
} else {
enable(); mo.observe(document, {childList:true, subtree:true});
}
document.addEventListener('click', intercept, true);
document.addEventListener('auxclick', intercept, true);
})();