V6.4.1: 修复域名提取bug,确保屏蔽功能100%生效
// ==UserScript==
// @name 功能增强型必应美化脚本 (Enhanced Bing Beautifier) - V6.4.1
// @namespace http://tampermonkey.net/
// @version 6.4.1
// @description V6.4.1: 修复域名提取bug,确保屏蔽功能100%生效
// @author Gemini & Enhanced by Assistant
// @match https://www.bing.com/search*
// @match https://cn.bing.com/search*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-start
// ==/UserScript==
(() => {
'use strict';
// ---------- 常量与配置 ----------
const CFG = {
STORAGE_KEYS: {
BLOCKLIST: 'bing_blocked_sites_v3',
},
UI: {
FAB_ID: 'search-enhancer-fab',
PANEL_ID: 'search-enhancer-panel',
BLOCKLIST_MODAL_ID: 'blocklist-modal',
BLOCKLIST_TEXTAREA_ID: 'blocklist-textarea',
},
SELECTORS: {
MAIN_CONTENT: '#b_content',
RIGHT_RAIL: '#b_context',
RESULTS_LIST: '#b_results',
CENTER_HAS: 'li.b_ans:has(#TechHelpInfoACFCard)',
CENTER_FALLBACK_PARENT: 'li.b_ans',
CENTER_FALLBACK_CHILD: '#TechHelpInfoACFCard',
MOVE_BOTTOM: 'li.b_vidAns',
SIDEBAR_HAS: 'li.b_ans:has(.df_alaskcarousel)',
SIDEBAR_FALLBACK_PARENT: 'li.b_ans',
SIDEBAR_FALLBACK_CHILD: '.df_alaskcarousel',
REMOVE: [
'li.b_algo.b_ad', // 广告搜索结果
'.b_ad', '.b_ads', '.sb_adsWv2', '.b_adTop',
'li.b_ans.b_ad', '.ad_sect_line', '.msan_ads_container', '#df_ad',
'li.b_ans:has(.b_rrsr)', '.b_msg.b_canvas', 'li.b_ans:has(#inline_rs)', 'li.b_ans:has(#brsv3)',
'li.b_ans:has(div.richrswrapper)', '#b_topw', '#b_pole', '.b_expandableAnswer',
'#brsv3', '#b_footer', '.b_inline_ajax_rs', '#inline_rs', '.richrswrapper', '.b_mrs',
'#monica-content-root', '#monica-search-enhance', '#doubao-ai-assistant', '#ciciai-shadow-container',
'doubao-ai-csui', 'kimi-web-extension', '#__infoflow_commercial', '#MAXAI_SEARCH_WITH_AI_ROOT_ID',
'cici-ai-csui', 'max-ai-minimum-app', 'use-chat-gpt-ai-content-menu', 'use-chat-gpt-ai',
'li.b_ans:has(.rqnaacfacc)', '#adstop_gradiant_separator',
],
RESULT_BLOCKS: '#b_results li.b_algo, #b_context li.b_ans',
CITE: 'cite',
ENHANCER_BTN: '.enhancer-btn',
QUERY_INPUT: '#sb_form_q',
},
PRESETS: {
SITES: [
{ name: '知乎', domain: 'zhihu.com', icon: '🎓' },
{ name: '哔哩哔哩', domain: 'bilibili.com', icon: '📺' },
{ name: 'GitHub', domain: 'github.com', icon: '💻' },
{ name: '维基百科', domain: 'wikipedia.org', icon: '📚' },
{ name: '豆瓣', domain: 'douban.com', icon: '🎬' },
{ name: '微博', domain: 'weibo.com', icon: '📱' },
{ name: '少数派', domain: 'sspai.com', icon: '⚡' },
{ name: 'CSDN', domain: 'csdn.net', icon: '💾' },
{ name: 'V2EX', domain: 'v2ex.com', icon: '🔧' },
{ name: 'Stack Overflow', domain: 'stackoverflow.com', icon: '📖' },
{ name: 'Reddit', domain: 'reddit.com', icon: '🌐' },
],
FILETYPES: [
{ name: 'PDF', ext: 'pdf', icon: '📄' },
{ name: 'DOCX', ext: 'docx', icon: '📝' },
{ name: 'PPTX', ext: 'pptx', icon: '📊' },
{ name: 'XLSX', ext: 'xlsx', icon: '📈' }
],
},
ADS_ICON_MARKERS: [
'/9j/4AAQSkZJRgABAQ',
'iVBORw0KGgoAAAANSUhEUgAAABsAAAALCAYAAACOAvbO'
],
};
// ---------- 工具函数 ----------
const CSS_HAS_SUPPORTED = typeof CSS !== 'undefined' && CSS.supports && CSS.supports('selector(:has(*))');
const debounce = (fn, wait = 150) => {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn.apply(null, args), wait);
};
};
const once = (fn) => {
let called = false;
return (...args) => {
if (called) return;
called = true;
fn(...args);
};
};
const qs = (root, sel) => root.querySelector(sel);
const qsa = (root, sel) => root.querySelectorAll(sel);
const domReady = (cb) => {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', cb, { once: true });
} else {
cb();
}
};
// ---------- 存储封装(异步) ----------
const store = {
async get(key, fallback = '[]') {
try {
const v = await GM_getValue(key, fallback);
return typeof v === 'string' ? JSON.parse(v) : v;
} catch {
return JSON.parse(fallback);
}
},
async set(key, value) {
await GM_setValue(key, JSON.stringify(value));
},
};
// ---------- 动效抽象层 ----------
const Effects = (() => {
const injectRippleCSS = once(() => {
GM_addStyle(`
.click-ripple {
position: absolute; border-radius: 50%;
transform: translate(-50%, -50%); pointer-events: none;
width: 8px; height: 8px;
background: radial-gradient(circle, rgba(255,255,255,0.9) 0%, rgba(0,120,212,0.2) 70%, rgba(0,120,212,0) 100%);
animation: ripple-anim 600ms ease-out forwards;
z-index: 10000;
}
@keyframes ripple-anim {
to { opacity: 0; transform: translate(-50%, -50%) scale(16); }
}
`);
});
function rippleAt(x, y) {
injectRippleCSS();
const el = document.createElement('div');
el.className = 'click-ripple';
el.style.left = x + 'px';
el.style.top = y + 'px';
document.body.appendChild(el);
setTimeout(() => el.remove(), 620);
}
function confettiAt(x, y, opts = {}) {
const g = window;
const hasCanvasConfetti = typeof g.confetti === 'function' && !!g.confetti;
if (hasCanvasConfetti) {
const canvas = document.createElement('canvas');
canvas.style.cssText = 'position:fixed;pointer-events:none;inset:0;z-index:10001;';
document.body.appendChild(canvas);
const my = g.confetti.create(canvas, { resize: true, useWorker: true });
my({
particleCount: opts.particleCount ?? 120,
spread: opts.spread ?? 75,
startVelocity: opts.startVelocity ?? 55,
gravity: opts.gravity ?? 0.9,
ticks: opts.ticks ?? 200,
origin: {
x: x / window.innerWidth,
y: y / window.innerHeight
},
scalar: opts.scalar ?? 1.0,
shapes: opts.shapes,
colors: opts.colors,
});
setTimeout(() => canvas.remove(), 1200);
} else {
rippleAt(x, y);
}
}
return {
burst: confettiAt,
ripple: rippleAt,
};
})();
// ---------- 样式注入(全面美化) ----------
function injectStyles() {
const S = CFG.SELECTORS;
GM_addStyle(`
:root {
--glass-bg: rgba(255, 255, 255, 0.75);
--glass-bg-dark: rgba(25, 25, 40, 0.8);
--glass-border: rgba(255, 255, 255, 0.4);
--glass-border-dark: rgba(255, 255, 255, 0.2);
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
--glass-shadow-hover: 0 12px 48px rgba(0, 0, 0, 0.15);
--card-radius: 20px;
--panel-radius: 16px;
--accent: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--accent-hover: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
--success: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
--danger: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
}
/* 背景美化 */
body {
background: url('https://raw.githubusercontent.com/WJH-makers/markdown_photos/main/images/sea.png') center/cover fixed no-repeat !important;
position: relative;
}
body::before {
content: '';
position: fixed;
inset: 0;
background: linear-gradient(180deg, rgba(103,126,234,0.05) 0%, rgba(118,75,162,0.05) 100%);
pointer-events: none;
z-index: -1;
}
/* 全局透明 */
#b_header, .b_scopebar, ${S.MAIN_CONTENT} { background: transparent !important; }
.b_header_bg, #b_header_bg { display: none !important; }
/* 移除广告 */
${CFG.SELECTORS.REMOVE.join(',')} { display: none !important; }
/* 居中容器 */
#gemini_centered_container {
width: 100%; display: flex; flex-direction: column; align-items: center; margin-bottom: 20px;
}
#gemini_centered_container > li[data-centered="true"] {
width: 70%; max-width: 800px; margin: 0 0 20px 0 !important; padding: 24px !important;
border-radius: var(--card-radius);
background: var(--glass-bg);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1.5px solid var(--glass-border);
box-shadow: var(--glass-shadow),
inset 0 1px 0 rgba(255,255,255,0.5) !important;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
#gemini_centered_container > li[data-centered="true"]:hover {
transform: translateY(-6px) scale(1.01);
box-shadow: var(--glass-shadow-hover),
inset 0 1px 0 rgba(255,255,255,0.6) !important;
}
body.b_dark #gemini_centered_container > li[data-centered="true"] {
background: var(--glass-bg-dark);
border-color: var(--glass-border-dark);
box-shadow: var(--glass-shadow),
inset 0 1px 0 rgba(255,255,255,0.1) !important;
}
/* 底部卡片 */
${S.MAIN_CONTENT} > li[data-relocated-bottom="true"] {
width: 90%; max-width: 1200px; margin: 40px auto 20px auto !important; padding: 24px !important;
border-radius: var(--card-radius);
background: var(--glass-bg);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1.5px solid var(--glass-border);
box-shadow: var(--glass-shadow),
inset 0 1px 0 rgba(255,255,255,0.5) !important;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
${S.MAIN_CONTENT} > li[data-relocated-bottom="true"]:hover {
transform: translateY(-4px);
box-shadow: var(--glass-shadow-hover),
inset 0 1px 0 rgba(255,255,255,0.6) !important;
}
body.b_dark ${S.MAIN_CONTENT} > li[data-relocated-bottom="true"] {
background: var(--glass-bg-dark);
border-color: var(--glass-border-dark);
}
/* 右栏卡片 */
${CFG.SELECTORS.RIGHT_RAIL} > li[data-relocated="true"] {
display: block !important; width: 100%; box-sizing: border-box;
margin: 0 0 20px 0; padding: 18px !important;
border-radius: 14px;
background: var(--glass-bg);
backdrop-filter: blur(16px) saturate(160%);
-webkit-backdrop-filter: blur(16px) saturate(160%);
border: 1px solid var(--glass-border);
box-shadow: 0 6px 24px rgba(0,0,0,0.08),
inset 0 1px 0 rgba(255,255,255,0.4) !important;
transition: all 0.3s ease;
}
${CFG.SELECTORS.RIGHT_RAIL} > li[data-relocated="true"]:hover {
transform: translateX(-4px);
box-shadow: 0 8px 32px rgba(0,0,0,0.12),
inset 0 1px 0 rgba(255,255,255,0.5) !important;
}
body.b_dark ${CFG.SELECTORS.RIGHT_RAIL} > li[data-relocated="true"] {
background: var(--glass-bg-dark);
border-color: var(--glass-border-dark);
}
/* 搜索结果卡片 - 高级玻璃态 */
${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad),
${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"]),
${CFG.SELECTORS.RIGHT_RAIL} .b_ans:not([data-relocated="true"]) {
background: var(--glass-bg) !important;
backdrop-filter: blur(24px) saturate(200%) !important;
-webkit-backdrop-filter: blur(24px) saturate(200%) !important;
border: 1.5px solid var(--glass-border) !important;
border-radius: var(--card-radius) !important;
padding: 22px !important;
margin-bottom: 24px !important;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15),
inset 0 1px 1px rgba(255,255,255,0.5),
0 1px 0 rgba(255,255,255,0.25) !important;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
/* 卡片高光效果 */
${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad)::before,
${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"])::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
transition: left 0.5s;
pointer-events: none;
}
${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad):hover::before,
${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"]):hover::before {
left: 100%;
}
/* 卡片悬停效果 */
${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad):hover,
${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"]):hover,
${CFG.SELECTORS.RIGHT_RAIL} .b_ans:not([data-relocated="true"]):hover {
transform: translateY(-6px) scale(1.01);
box-shadow: 0 16px 48px rgba(31, 38, 135, 0.2),
inset 0 2px 4px rgba(255,255,255,0.6),
0 2px 0 rgba(255,255,255,0.4) !important;
background: rgba(255,255,255,0.85) !important;
border-color: rgba(255,255,255,0.6) !important;
}
/* 暗色模式 */
body.b_dark ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad),
body.b_dark ${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"]),
body.b_dark ${CFG.SELECTORS.RIGHT_RAIL} .b_ans:not([data-relocated="true"]) {
background: var(--glass-bg-dark) !important;
border-color: var(--glass-border-dark) !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3),
inset 0 1px 1px rgba(255,255,255,0.1) !important;
}
body.b_dark ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad):hover,
body.b_dark ${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"]):hover,
body.b_dark ${CFG.SELECTORS.RIGHT_RAIL} .b_ans:not([data-relocated="true"]):hover {
background: rgba(25,25,40,0.9) !important;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4),
inset 0 2px 4px rgba(255,255,255,0.15) !important;
}
/* 文字美化 */
.b_algo h2 a {
color: #1a1a2e !important;
font-weight: 600;
transition: color 0.3s ease;
}
.b_algo h2 a:hover {
color: #667eea !important;
}
body.b_dark .b_algo h2 a { color: #e0e0ff !important; }
body.b_dark .b_algo h2 a:hover { color: #a5b4fc !important; }
.b_caption p, .b_caption .b_lineclamp3 {
color: #4a4a4a !important;
line-height: 1.6;
}
body.b_dark .b_caption p,
body.b_dark .b_caption .b_lineclamp3 { color: #d0d0d0 !important; }
cite {
color: #16a34a !important;
font-style: normal;
font-weight: 500;
transition: color 0.3s ease;
}
cite:hover { color: #15803d !important; }
body.b_dark cite { color: #86efac !important; }
body.b_dark cite:hover { color: #4ade80 !important; }
/* FAB 按钮 - 呼吸动画 */
#search-enhancer-fab {
position: fixed; top: 120px; right: 20px; z-index: 9999;
width: 56px; height: 56px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 24px; cursor: pointer;
backdrop-filter: blur(10px);
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.4),
inset 0 1px 0 rgba(255,255,255,0.3);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
animation: fab-breathe 3s ease-in-out infinite;
}
@keyframes fab-breathe {
0%, 100% { transform: scale(1); box-shadow: 0 8px 24px rgba(102, 126, 234, 0.4); }
50% { transform: scale(1.05); box-shadow: 0 12px 32px rgba(102, 126, 234, 0.6); }
}
#search-enhancer-fab:hover {
transform: scale(1.12) rotate(90deg);
box-shadow: 0 12px 36px rgba(102, 126, 234, 0.6),
inset 0 2px 0 rgba(255,255,255,0.4);
animation: none;
}
#search-enhancer-fab:active {
transform: scale(1.05) rotate(90deg);
}
/* 面板 - 现代卡片设计 */
#search-enhancer-panel {
position: fixed; top: 190px; right: 20px; z-index: 9998;
width: 360px; padding: 0;
background: var(--glass-bg);
backdrop-filter: blur(30px) saturate(180%);
-webkit-backdrop-filter: blur(30px) saturate(180%);
border-radius: var(--panel-radius);
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255,255,255,0.5);
border: 1.5px solid var(--glass-border);
display: none;
overflow: hidden;
animation: panel-slide-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes panel-slide-in {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
body.b_dark #search-enhancer-panel {
background: var(--glass-bg-dark);
border-color: var(--glass-border-dark);
}
/* 面板组 */
.enhancer-group {
padding: 16px 18px;
border-bottom: 1px solid rgba(0,0,0,0.06);
transition: background 0.2s ease;
}
.enhancer-group:last-child {
border-bottom: none;
}
.enhancer-group:hover {
background: rgba(255,255,255,0.3);
}
body.b_dark .enhancer-group {
border-bottom-color: rgba(255,255,255,0.08);
}
body.b_dark .enhancer-group:hover {
background: rgba(255,255,255,0.05);
}
.enhancer-group h3 {
font-size: 13px;
font-weight: 700;
margin: 0 0 12px 0;
color: #374151;
text-transform: uppercase;
letter-spacing: 0.5px;
display: flex;
align-items: center;
gap: 6px;
}
body.b_dark .enhancer-group h3 { color: #e5e7eb; }
/* 按钮网格 */
.enhancer-btn-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
/* 按钮 - 渐变 + 动画 */
.enhancer-btn {
padding: 8px 14px;
border-radius: 10px;
border: none;
cursor: pointer;
background: var(--accent);
color: #fff;
font-size: 13px;
font-weight: 600;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
position: relative;
overflow: hidden;
}
.enhancer-btn::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255,255,255,0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.enhancer-btn:hover::before {
width: 300px;
height: 300px;
}
.enhancer-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4);
background: var(--accent-hover);
}
.enhancer-btn:active {
transform: translateY(0) scale(0.98);
}
/* 自定义输入组 */
.custom-input-group {
display: flex;
gap: 8px;
margin-top: 10px;
}
.custom-input-group input {
flex-grow: 1;
padding: 8px 12px;
border-radius: 10px;
border: 1.5px solid rgba(102, 126, 234, 0.3);
background: rgba(255,255,255,0.5);
backdrop-filter: blur(10px);
font-size: 13px;
transition: all 0.3s ease;
color: #1a1a2e;
}
.custom-input-group input:focus {
outline: none;
border-color: #667eea;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
body.b_dark .custom-input-group input {
background: rgba(255,255,255,0.1);
border-color: rgba(255,255,255,0.2);
color: #e5e7eb;
}
body.b_dark .custom-input-group input:focus {
background: rgba(255,255,255,0.15);
border-color: #a5b4fc;
}
/* 屏蔽按钮 */
.block-site-btn {
margin-left: 8px;
cursor: pointer;
color: #ef4444;
font-weight: 600;
font-size: 12px;
padding: 2px 8px;
border-radius: 6px;
transition: all 0.2s ease;
display: inline-block;
}
.block-site-btn:hover {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
transform: scale(1.05);
}
.block-site-btn.blocked {
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
color: white !important;
cursor: default;
font-weight: 500;
}
.block-site-btn.blocked:hover {
transform: none;
}
/* 模态框 */
#blocklist-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10001;
width: 540px;
max-width: 90vw;
padding: 28px;
background: var(--glass-bg);
backdrop-filter: blur(40px) saturate(200%);
-webkit-backdrop-filter: blur(40px) saturate(200%);
border-radius: 20px;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255,255,255,0.6);
border: 2px solid var(--glass-border);
display: none;
animation: modal-zoom-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes modal-zoom-in {
from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
body.b_dark #blocklist-modal {
background: var(--glass-bg-dark);
border-color: var(--glass-border-dark);
}
#blocklist-modal h2 {
color: #1a1a2e;
margin: 0 0 8px 0;
font-size: 22px;
font-weight: 700;
}
#blocklist-modal p {
color: #6b7280;
margin: 0 0 16px 0;
font-size: 14px;
}
body.b_dark #blocklist-modal h2 { color: #f3f4f6; }
body.b_dark #blocklist-modal p { color: #9ca3af; }
#blocklist-textarea {
width: calc(100% - 24px);
height: 240px;
padding: 12px;
border-radius: 12px;
border: 2px solid rgba(102, 126, 234, 0.3);
background: rgba(255,255,255,0.6);
backdrop-filter: blur(10px);
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
color: #1a1a2e;
resize: vertical;
transition: all 0.3s ease;
}
#blocklist-textarea:focus {
outline: none;
border-color: #667eea;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
}
body.b_dark #blocklist-textarea {
background: rgba(255,255,255,0.08);
border-color: rgba(255,255,255,0.2);
color: #e5e7eb;
}
body.b_dark #blocklist-textarea:focus {
background: rgba(255,255,255,0.12);
border-color: #a5b4fc;
}
.blocklist-buttons {
margin-top: 18px;
display: flex;
gap: 10px;
justify-content: flex-end;
}
.blocklist-buttons .enhancer-btn {
min-width: 80px;
}
#blocklist-save-btn {
background: var(--success);
}
#blocklist-save-btn:hover {
background: linear-gradient(135deg, #16a34a 0%, #22c55e 100%);
}
#blocklist-cancel-btn {
background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);
}
#blocklist-cancel-btn:hover {
background: linear-gradient(135deg, #4b5563 0%, #6b7280 100%);
}
/* [V6.4.1] 强制隐藏已屏蔽的结果 */
li.b_algo[data-blocked="true"],
li.b_ans[data-blocked="true"] {
display: none !important;
}
`);
}
// ---------- DOM 操作 ----------
const Containers = {
ensureCenteredHost() {
if (!document.getElementById('gemini_centered_container')) {
const c = document.createElement('div');
c.id = 'gemini_centered_container';
const main = qs(document, CFG.SELECTORS.MAIN_CONTENT);
if (main) main.before(c);
}
return document.getElementById('gemini_centered_container');
},
relocateToCenter(root) {
const host = this.ensureCenteredHost();
if (!host) return;
if (CSS_HAS_SUPPORTED) {
root.querySelectorAll(CFG.SELECTORS.CENTER_HAS).forEach(el => {
if (!el.dataset.centered) {
el.dataset.centered = 'true';
host.appendChild(el);
}
});
} else {
root.querySelectorAll(CFG.SELECTORS.CENTER_FALLBACK_PARENT).forEach(li => {
if (li.dataset.centered) return;
if (li.querySelector(CFG.SELECTORS.CENTER_FALLBACK_CHILD)) {
li.dataset.centered = 'true';
host.appendChild(li);
}
});
}
},
relocateToEnd(root) {
const main = qs(document, CFG.SELECTORS.MAIN_CONTENT);
if (!main) return;
root.querySelectorAll(CFG.SELECTORS.MOVE_BOTTOM).forEach(el => {
if (!el.dataset.relocatedBottom) {
el.dataset.relocatedBottom = 'true';
main.appendChild(el);
}
});
},
relocateToSidebar(root) {
const rail = qs(document, CFG.SELECTORS.RIGHT_RAIL);
if (!rail) return;
rail.style.display = 'inline-block';
rail.style.verticalAlign = 'top';
if (CSS_HAS_SUPPORTED) {
root.querySelectorAll(CFG.SELECTORS.SIDEBAR_HAS).forEach(el => {
if (!el.dataset.relocated) {
el.dataset.relocated = 'true';
rail.prepend(el);
}
});
} else {
root.querySelectorAll(CFG.SELECTORS.SIDEBAR_FALLBACK_PARENT).forEach(li => {
if (li.dataset.relocated) return;
if (li.querySelector(CFG.SELECTORS.SIDEBAR_FALLBACK_CHILD)) {
li.dataset.relocated = 'true';
rail.prepend(li);
}
});
}
},
};
// ---------- 广告探测 ----------
let cachedAdSelectors = [];
let scannedSheets = false;
function scanStylesheetsForAdMarkers() {
if (scannedSheets || CFG.ADS_ICON_MARKERS.length === 0) return;
const set = new Set();
for (const sheet of document.styleSheets) {
try {
const rules = sheet.cssRules;
if (!rules) continue;
for (const rule of rules) {
if (rule.style && rule.style.content) {
const content = rule.style.content;
for (const marker of CFG.ADS_ICON_MARKERS) {
if (content.includes(marker)) {
const sels = rule.selectorText.split(',').map(s => s.replace(/::(before|after)/gi, '').trim());
sels.forEach(s => set.add(s));
}
}
}
}
} catch { /* ignore CORS */ }
}
cachedAdSelectors = Array.from(set);
scannedSheets = true;
}
function applyDynamicAdBlocking(root) {
if (!cachedAdSelectors.length) return;
cachedAdSelectors.forEach(sel => {
try {
root.querySelectorAll(sel).forEach(el => {
const block = el.closest('li.b_algo, .b_ans');
if (block && block.style.display !== 'none') block.style.display = 'none';
});
} catch { /* ignore invalid selectors */ }
});
}
// ---------- 站点屏蔽(修复域名提取) ----------
let blockedSites = [];
/**
* [V6.4.1 修复] 从cite元素提取域名
* 正确处理特殊字符(›、»、空格等)
*/
function domainFromCite(citeEl) {
if (!citeEl || !citeEl.textContent) return null;
const t = citeEl.textContent.trim();
// 处理特殊字符分隔符,只取URL部分
// \u203A = ›, \u00BB = », 还有中文空格等
const urlPart = t.split(/[\s\u203A\u00BB›»\u3000]+/)[0];
// 只匹配合法的域名字符(RFC标准)
const m = urlPart.match(/^(?:https?:\/\/)?(?:www\.)?([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*)/i);
const domain = m ? m[1].toLowerCase() : null;
// 调试日志(可选)
if (domain) {
console.debug(`[域名提取] "${t}" => "${domain}"`);
}
return domain;
}
/**
* [V6.4.1] 应用静态屏蔽
*/
function applyStaticBlocking(root) {
const resultItemSelector = 'li.b_algo:not(.b_nwsAns), li.b_ans:not(.b_nwsAns)';
const itemsInRoot = Array.from(root.querySelectorAll(resultItemSelector));
let allItems = itemsInRoot;
if (root.nodeType === 1 && root.matches && root.matches(resultItemSelector)) {
allItems.push(root);
}
[...new Set(allItems)]
.filter(item => item.closest('#b_results, #b_context'))
.forEach(result => {
const cite = result.querySelector(CFG.SELECTORS.CITE);
const domain = domainFromCite(cite);
if (domain && blockedSites.includes(domain)) {
result.dataset.blocked = 'true';
result.style.display = 'none';
console.debug(`[屏蔽] ${domain}`);
}
});
}
/**
* [V6.4.1] 添加屏蔽按钮
*/
function addBlockButtons(root) {
const resultItemSelector = 'li.b_algo:not(.b_nwsAns), li.b_ans:not(.b_nwsAns)';
const itemsInRoot = Array.from(root.querySelectorAll(resultItemSelector));
let allItems = itemsInRoot;
if (root.nodeType === 1 && root.matches && root.matches(resultItemSelector)) {
allItems.push(root);
}
[...new Set(allItems)]
.filter(item => item.closest('#b_results, #b_context'))
.forEach(result => {
if (result.querySelector('.block-site-btn')) return;
const cite = result.querySelector(CFG.SELECTORS.CITE);
const domain = domainFromCite(cite);
if (!domain || !cite || !cite.parentNode) return;
const btn = document.createElement('span');
btn.className = 'block-site-btn';
if (blockedSites.includes(domain)) {
btn.textContent = '✓ 已屏蔽';
btn.classList.add('blocked');
} else {
btn.textContent = '🚫 屏蔽';
btn.title = `屏蔽 ${domain}`;
btn.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
// 更新按钮状态
btn.textContent = '✓ 已屏蔽';
btn.classList.add('blocked');
btn.title = '';
// 粒子效果
Effects.burst(e.clientX, e.clientY, {
particleCount: 80,
spread: 65,
colors: ['#22c55e', '#16a34a', '#4ade80', '#86efac']
});
// 动画隐藏
result.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
result.style.opacity = '0';
result.style.transform = 'scale(0.95)';
setTimeout(() => {
result.dataset.blocked = 'true';
result.style.display = 'none';
}, 300);
// 保存到屏蔽列表
if (!blockedSites.includes(domain)) {
blockedSites.push(domain);
await store.set(CFG.STORAGE_KEYS.BLOCKLIST, [...new Set(blockedSites)]);
console.log(`[已添加屏蔽] ${domain}`);
}
}, { once: true });
}
cite.parentNode.appendChild(btn);
});
}
// ---------- 搜索增强 UI ----------
function currentQuery() {
const q = qs(document, CFG.SELECTORS.QUERY_INPUT);
let s = q ? q.value : '';
return s.replace(/"/g, '').replace(/-\S+/g, '').replace(/site:\S+/g, '').replace(/filetype:\S+/g, '').trim();
}
function doSearch(str) {
const params = new URLSearchParams(window.location.search);
params.set('q', str.trim());
window.location.search = params.toString();
}
function createUI() {
if (document.getElementById(CFG.UI.FAB_ID)) return;
// FAB
const fab = document.createElement('div');
fab.id = CFG.UI.FAB_ID;
fab.innerHTML = '🛠️';
fab.title = '搜索增强工具';
document.body.appendChild(fab);
// 面板
const panel = document.createElement('div');
panel.id = CFG.UI.PANEL_ID;
panel.innerHTML = `
<div class="enhancer-group">
<h3>🎯 搜索模式</h3>
<div class="enhancer-btn-grid">
<button class="enhancer-btn" data-op="quotes">精确匹配</button>
<button class="enhancer-btn" data-op="broaden">扩大范围</button>
</div>
</div>
<div class="enhancer-group">
<h3>⏰ 时间范围</h3>
<div class="enhancer-btn-grid">
<button class="enhancer-btn" data-filter-param="qft" data-filter-value="+filterui:age-lt1440">24小时</button>
<button class="enhancer-btn" data-filter-param="qft" data-filter-value="+filterui:age-lt10080">一周</button>
<button class="enhancer-btn" data-filter-param="qft" data-filter-value="+filterui:age-lt43200">一月</button>
<button class="enhancer-btn" data-filter-param="qft" data-filter-value="+filterui:age-lt525600">一年</button>
</div>
</div>
<div class="enhancer-group">
<h3>🌐 站内搜索</h3>
<div class="enhancer-btn-grid" id="site-preset-grid"></div>
<div class="custom-input-group">
<input id="custom-site-input" type="text" placeholder="输入域名...">
<button id="custom-site-btn" class="enhancer-btn" data-op="custom-site">搜索</button>
</div>
</div>
<div class="enhancer-group">
<h3>📁 文件类型</h3>
<div class="enhancer-btn-grid" id="filetype-preset-grid"></div>
</div>
<div class="enhancer-group">
<h3>🛡️ 内容屏蔽</h3>
<button id="enhancer-blocklist-btn" class="enhancer-btn" style="width: 100%;">管理屏蔽列表</button>
</div>
`;
document.body.appendChild(panel);
// 预置按钮
const siteGrid = document.getElementById('site-preset-grid');
CFG.PRESETS.SITES.forEach(site => {
const b = document.createElement('button');
b.className = 'enhancer-btn';
b.innerHTML = `${site.icon} ${site.name}`;
b.dataset.op = 'site';
b.dataset.value = site.domain;
siteGrid.appendChild(b);
});
const fileGrid = document.getElementById('filetype-preset-grid');
CFG.PRESETS.FILETYPES.forEach(type => {
const b = document.createElement('button');
b.className = 'enhancer-btn';
b.innerHTML = `${type.icon} ${type.name}`;
b.dataset.op = 'filetype';
b.dataset.value = type.ext;
fileGrid.appendChild(b);
});
// 模态框
const modal = document.createElement('div');
modal.id = CFG.UI.BLOCKLIST_MODAL_ID;
modal.innerHTML = `
<h2>🛡️ 网站屏蔽列表</h2>
<p>每行输入一个域名(例如:qiniu.com),保存后自动刷新</p>
<textarea id="${CFG.UI.BLOCKLIST_TEXTAREA_ID}" placeholder="qiniu.com zhihu.com csdn.net"></textarea>
<div class="blocklist-buttons">
<button id="blocklist-cancel-btn" class="enhancer-btn">取消</button>
<button id="blocklist-save-btn" class="enhancer-btn">保存并刷新</button>
</div>
`;
document.body.appendChild(modal);
// 交互:FAB
fab.addEventListener('click', (e) => {
const isVisible = panel.style.display === 'block';
panel.style.display = isVisible ? 'none' : 'block';
Effects.burst(e.clientX, e.clientY, {
particleCount: isVisible ? 40 : 100,
spread: isVisible ? 45 : 75,
colors: isVisible ? ['#6b7280'] : ['#667eea', '#764ba2', '#a5b4fc']
});
}, { passive: true });
// 交互:面板按钮
panel.addEventListener('click', (ev) => {
const target = ev.target.closest('.enhancer-btn');
if (!target) return;
const { op, value, filterParam, filterValue } = target.dataset;
const base = currentQuery();
const x = ev.clientX || (target.getBoundingClientRect().left + 16);
const y = ev.clientY || (target.getBoundingClientRect().top + 8);
Effects.burst(x, y, {
particleCount: 50,
spread: 55,
colors: ['#667eea', '#764ba2', '#a5b4fc', '#c4b5fd']
});
let nq = base;
switch (op) {
case 'quotes': nq = `"${base}"`; break;
case 'broaden': nq = base; break;
case 'site': nq = `${base} site:${value}`; break;
case 'custom-site':
const domain = document.getElementById('custom-site-input').value.trim();
if (domain) nq = `${base} site:${domain}`;
break;
case 'filetype': nq = `${base} filetype:${value}`; break;
}
if (filterParam && filterValue) {
const params = new URLSearchParams(window.location.search);
params.set('q', nq);
params.set(filterParam, filterValue);
window.location.search = params.toString();
} else if (nq !== base || op === 'broaden') {
doSearch(nq);
}
}, { passive: true });
// 交互:屏蔽列表
document.getElementById('enhancer-blocklist-btn').addEventListener('click', async (e) => {
const list = await store.get(CFG.STORAGE_KEYS.BLOCKLIST, '[]');
document.getElementById(CFG.UI.BLOCKLIST_TEXTAREA_ID).value = list.join('\n');
modal.style.display = 'block';
Effects.burst(e.clientX, e.clientY, {
particleCount: 60,
spread: 50,
colors: ['#ef4444', '#dc2626', '#f87171']
});
}, { passive: true });
document.getElementById('blocklist-save-btn').addEventListener('click', async (e) => {
const lines = document.getElementById(CFG.UI.BLOCKLIST_TEXTAREA_ID).value
.split('\n')
.map(s => s.trim().toLowerCase())
.filter(Boolean);
await store.set(CFG.STORAGE_KEYS.BLOCKLIST, [...new Set(lines)]);
Effects.burst(e.clientX, e.clientY, {
particleCount: 120,
spread: 80,
colors: ['#22c55e', '#16a34a', '#4ade80', '#86efac']
});
setTimeout(() => {
window.location.reload();
}, 600);
}, { passive: true });
document.getElementById('blocklist-cancel-btn').addEventListener('click', (e) => {
modal.style.display = 'none';
Effects.ripple(e.clientX, e.clientY);
}, { passive: true });
}
// ---------- 统一处理 ----------
function processNode(root) {
Containers.relocateToCenter(root);
Containers.relocateToEnd(root);
Containers.relocateToSidebar(root);
applyDynamicAdBlocking(root);
applyStaticBlocking(root);
addBlockButtons(root);
}
// ---------- 初始化与观察 ----------
let globalObserver = null;
const mutationHandler = debounce((mutations) => {
for (const m of mutations) {
if (m.type === 'childList' && m.addedNodes.length) {
m.addedNodes.forEach(n => {
if (n.nodeType === Node.ELEMENT_NODE) {
processNode(n);
}
});
}
}
}, 120);
function initialRun() {
Containers.ensureCenteredHost();
scanStylesheetsForAdMarkers();
processNode(document.body);
}
function setupObserver() {
if (globalObserver) {
globalObserver.disconnect();
}
const targets = [
qs(document, CFG.SELECTORS.MAIN_CONTENT),
qs(document, CFG.SELECTORS.RIGHT_RAIL),
qs(document, CFG.SELECTORS.RESULTS_LIST),
].filter(Boolean);
if (targets.length > 0) {
globalObserver = new MutationObserver(mutationHandler);
targets.forEach(t => globalObserver.observe(t, { childList: true, subtree: true }));
}
}
function mainInit() {
createUI();
initialRun();
setupObserver();
}
// ---------- PJAX/SPA 导航处理 ----------
function onPageChange() {
setTimeout(() => {
mainInit();
}, 300);
}
(function setupNavListener() {
let lastHref = window.location.href;
['pushState', 'replaceState'].forEach(fn => {
const orig = history[fn];
if (orig.patched) return;
history[fn] = function() {
const ret = orig.apply(this, arguments);
window.dispatchEvent(new Event('locationchange_internal'));
return ret;
};
history[fn].patched = true;
});
window.addEventListener('popstate', onPageChange);
window.addEventListener('locationchange_internal', onPageChange);
setInterval(() => {
if (window.location.href !== lastHref) {
lastHref = window.location.href;
onPageChange();
}
}, 250);
})();
// ---------- 启动 ----------
injectStyles();
(async () => {
blockedSites = await store.get(CFG.STORAGE_KEYS.BLOCKLIST, '[]');
console.log('[屏蔽列表]', blockedSites);
domReady(() => {
mainInit();
});
})();
})();