Google・DuckDuckGoの検索結果ページにて、特定サイトをブロックします。
// ==UserScript==
// @name Search Result Blocker
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Google・DuckDuckGoの検索結果ページにて、特定サイトをブロックします。
// @description 各検索結果をマウスホバーすると、右側に×ボタンが表示され、クリックすると検索結果から非表示されるようになります。
// @description ブロックリストは右下のオプションメニューから個別に削除できます。また、ブロックリストをファイルとして書き出し/読み込みすることができます。
// @author Bookyakuno
// @match https://www.google.com/search*
// @match https://www.google.co.jp/search*
// @match https://duckduckgo.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// CSS変数とスタイル
GM_addStyle(`
:root {
--gb-bg: #ffffff;
--gb-text: #202124;
--gb-border: #dfe1e5;
--gb-shadow: rgba(0,0,0,0.2);
--gb-hover: #f1f3f4;
--gb-accent: #1a73e8;
}
@media (prefers-color-scheme: dark) {
:root {
--gb-bg: #202124;
--gb-text: #e8eaed;
--gb-border: #3c4043;
--gb-shadow: rgba(0,0,0,0.5);
--gb-hover: #303134;
--gb-accent: #8ab4f8;
}
}
.block-btn {
opacity: 0;
transition: opacity 0.2s ease;
margin-left: 10px;
cursor: pointer;
color: #70757a;
font-size: 12px;
border: 1px solid var(--gb-border);
border-radius: 4px;
padding: 0 6px;
background: var(--gb-bg);
display: inline-block;
text-decoration: none !important;
}
.g:hover .block-btn, .tF2Cxc:hover .block-btn,
article:hover .block-btn, .nrn-react-div:hover .block-btn {
opacity: 1;
}
.block-btn:hover { background: #d93025 !important; color: white !important; border-color: #d93025; }
#block-config-gear {
position: fixed; bottom: 25px; right: 25px; z-index: 99999;
cursor: pointer; font-size: 20px; background: var(--gb-bg); color: var(--gb-text);
border: 1px solid var(--gb-border); border-radius: 50%; width: 40px; height: 40px;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 2px 8px var(--gb-shadow); opacity: 0.1; transition: 0.3s;
}
#block-config-gear:hover { opacity: 1.0; transform: rotate(45deg); }
#block-menu {
position: fixed; bottom: 75px; right: 25px; width: 300px; max-height: 450px;
background: var(--gb-bg); color: var(--gb-text); border: 1px solid var(--gb-border);
padding: 16px; display: none; z-index: 99999; box-shadow: 0 4px 15px var(--gb-shadow);
overflow-y: auto; border-radius: 12px; font-family: sans-serif;
}
#block-menu h3 { margin: 0 0 12px 0; font-size: 16px; border-bottom: 1px solid var(--gb-border); padding-bottom: 8px; }
.block-list-container { max-height: 200px; overflow-y: auto; margin-bottom: 15px; border: 1px solid var(--gb-border); border-radius: 4px; padding: 5px; }
.block-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; font-size: 12px; }
.remove-link { color: #d93025; cursor: pointer; padding: 2px 5px; }
.menu-footer { display: flex; gap: 8px; border-top: 1px solid var(--gb-border); pt: 10px; margin-top: 10px; padding-top: 10px; }
.footer-btn {
flex: 1; font-size: 11px; padding: 6px; cursor: pointer; border-radius: 4px;
text-align: center; border: 1px solid var(--gb-border); background: var(--gb-bg); color: var(--gb-text);
}
.footer-btn:hover { background: var(--gb-hover); }
`);
let blockedDomains = GM_getValue('blockedDomains', []);
const config = {
'google': { itemSelector: '.g, .tF2Cxc', titleSelector: 'h3', getLink: (el) => el.querySelector('a')?.href },
'duckduckgo': { itemSelector: 'article, .nrn-react-div', titleSelector: 'h2, [data-testid="result-title-a"]', getLink: (el) => el.querySelector('a[data-testid="result-title-a"]')?.href || el.querySelector('a')?.href }
};
function applyFilter() {
const isGoogle = window.location.hostname.includes('google');
const site = config[isGoogle ? 'google' : 'duckduckgo'];
const results = document.querySelectorAll(site.itemSelector);
results.forEach(el => {
const linkHref = site.getLink(el);
if (!linkHref || linkHref.startsWith('/') || linkHref.includes('google.com/search')) return;
let hostname = new URL(linkHref).hostname;
if (!el.querySelector('.block-btn')) {
const btn = document.createElement('span');
btn.className = 'block-btn'; btn.innerText = '×';
btn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); blockDomain(hostname); };
const title = el.querySelector(site.titleSelector);
if (title) { title.style.display = 'inline-block'; title.after(btn); }
}
el.style.display = blockedDomains.includes(hostname) ? 'none' : '';
});
}
function blockDomain(domain) {
if (!blockedDomains.includes(domain)) {
blockedDomains.push(domain);
saveAndRefresh();
}
}
function unblockDomain(domain) {
blockedDomains = blockedDomains.filter(d => d !== domain);
saveAndRefresh();
}
function saveAndRefresh() {
GM_setValue('blockedDomains', blockedDomains);
applyFilter();
renderMenu();
}
// エクスポート機能
function exportList() {
const blob = new Blob([JSON.stringify(blockedDomains, null, 2)], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'blocklist.json';
a.click();
URL.revokeObjectURL(url);
}
// インポート機能
function importList() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (event) => {
try {
const imported = JSON.parse(event.target.result);
if (Array.isArray(imported)) {
blockedDomains = [...new Set([...blockedDomains, ...imported])];
saveAndRefresh();
alert('インポート完了しました');
}
} catch (err) { alert('不正なファイル形式です'); }
};
reader.readAsText(file);
};
input.click();
}
function initUI() {
const gear = document.createElement('div');
gear.id = 'block-config-gear'; gear.innerHTML = '⚙️';
gear.onclick = (e) => { e.stopPropagation(); toggleMenu(); };
document.body.appendChild(gear);
const menu = document.createElement('div');
menu.id = 'block-menu';
menu.onclick = (e) => e.stopPropagation();
document.body.appendChild(menu);
document.addEventListener('click', () => { if (menu.style.display === 'block') menu.style.display = 'none'; });
renderMenu();
}
function toggleMenu() {
const menu = document.getElementById('block-menu');
menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
}
function renderMenu() {
const menu = document.getElementById('block-menu');
menu.innerHTML = '<h3>ブロックリスト</h3>';
const listCont = document.createElement('div');
listCont.className = 'block-list-container';
if (blockedDomains.length === 0) {
listCont.innerHTML = '<p style="font-size:11px; color:#797979; text-align:center;">空です</p>';
}
blockedDomains.forEach(domain => {
const item = document.createElement('div');
item.className = 'block-item';
item.innerHTML = `<span title="${domain}" style="overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:200px;">${domain}</span>`;
const remove = document.createElement('span');
remove.className = 'remove-link'; remove.innerText = '解除';
remove.onclick = () => unblockDomain(domain);
item.appendChild(remove);
listCont.appendChild(item);
});
menu.appendChild(listCont);
// フッターボタン
const footer = document.createElement('div');
footer.className = 'menu-footer';
const btnExp = document.createElement('div');
btnExp.className = 'footer-btn'; btnExp.innerText = '書き出し';
btnExp.onclick = exportList;
const btnImp = document.createElement('div');
btnImp.className = 'footer-btn'; btnImp.innerText = '読み込み';
btnImp.onclick = importList;
footer.appendChild(btnExp);
footer.appendChild(btnImp);
menu.appendChild(footer);
}
const observer = new MutationObserver(applyFilter);
observer.observe(document.body, { childList: true, subtree: true });
initUI();
applyFilter();
})();