Google 搜索结果域名修正(v2ex)
见code
// ==UserScript==
// @name Google 搜索结果域名修正(v2ex)
// @namespace jimmy.plugin.v2ex-domain-fix
// @version 1.1
// @description 将 Google 搜索结果中 v2ex.com 的三级域名链接(如 www.v2ex.com、hk.v2ex.com)改写为可正常访问的二级域名
// @match https://www.google.com/*
// @match https://www.google.com.hk/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function () {
'use strict';
// 需要"三级域名 -> 二级域名"归一化处理的根域名列表。
// 只有 v2ex.com 本身可访问,www.v2ex.com / hk.v2ex.com 等子域名均需改写为 v2ex.com。
// 后续如需支持其他站点,直接往数组里追加根域名即可。
const ROOT_DOMAINS = ['v2ex.com'];
const DOMAIN_TEXT_RE = new RegExp(
ROOT_DOMAINS.map((r) => '([a-z0-9-]+\\.)+' + r.replace('.', '\\.')).join('|'),
'gi'
);
function matchRootDomain(hostname) {
for (const root of ROOT_DOMAINS) {
if (hostname === root) return null; // 已经是二级域名,无需处理
if (hostname.toLowerCase().endsWith('.' + root)) return root;
}
return null;
}
// 计算改写后的 URL,命中返回新地址,未命中返回 null。
function fixedHref(href) {
let url;
try {
url = new URL(href, location.href);
} catch {
return null;
}
const root = matchRootDomain(url.hostname);
if (!root) return null;
url.hostname = root;
return url.toString();
}
function fixLink(a) {
const fixed = fixedHref(a.href);
if (fixed) a.href = fixed;
}
// 逐文本节点替换展示用的域名文字,不依赖具体标签( 等),避免 Google 改版后失效,
// 也不会像直接改 el.textContent 那样把子节点结构一并破坏掉。
function fixTextNodesUnder(root) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
let node;
while ((node = walker.nextNode())) {
if (DOMAIN_TEXT_RE.test(node.nodeValue)) {
node.nodeValue = node.nodeValue.replace(DOMAIN_TEXT_RE, (m) => {
for (const r of ROOT_DOMAINS) {
if (m.toLowerCase().endsWith(r)) return r;
}
return m;
});
}
}
}
function scan(root) {
let fixedCount = 0;
root.querySelectorAll('a[href]').forEach((a) => {
const before = a.href;
fixLink(a);
if (a.href !== before) fixedCount++;
});
fixTextNodesUnder(root);
if (fixedCount > 0) {
console.log('[v2ex-domain-fix] 本次修正了', fixedCount, '个链接');
}
}
scan(document.body);
// Google 搜索结果会在滚动加载更多 / 切换 Tab 时动态插入新节点,用 MutationObserver 持续处理。
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
scan(node);
}
});
}
});
observer.observe(document.body, { childList: true, subtree: true });
// 兜底:万一某个链接因渲染时机没被扫描到,点击瞬间也强制纠正跳转地址,
// 用 capture 阶段保证在浏览器发起导航之前完成改写。
document.addEventListener(
'click',
(e) => {
const a = e.target instanceof Element ? e.target.closest('a[href]') : null;
if (!a) return;
const fixed = fixedHref(a.href);
if (fixed && fixed !== a.href) {
console.log('[v2ex-domain-fix] 点击兜底修正:', a.href, '->', fixed);
a.href = fixed;
}
},
true
);
})();