您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在排行页面右上角增加按钮,可开启屏蔽模式进行条目屏蔽或管理已屏蔽条目,支持本地持久保存。
// ==UserScript== // @name Bangumi排行榜条目屏蔽器 // @namespace https://greasyfork.org/zh-CN/scripts/544368 // @version 1.2 // @description 在排行页面右上角增加按钮,可开启屏蔽模式进行条目屏蔽或管理已屏蔽条目,支持本地持久保存。 // @author forary // @license MIT // @match https://bgm.tv/*/browser*sort=rank* // @match https://bangumi.tv/*/browser*sort=rank* // @match https://chii.in/*/browser*sort=rank* // @grant none // ==/UserScript== (function () { 'use strict'; const category = (function () { const path = location.pathname; if (path.startsWith('/anime/')) return 'anime'; if (path.startsWith('/book/')) return 'book'; if (path.startsWith('/music/')) return 'music'; if (path.startsWith('/game/')) return 'game'; if (path.startsWith('/real/')) return 'real'; return 'wrong'; })(); const STORAGE_KEY = `bangumi_rank_blocked_items_${category}`; const SETTINGS_KEY = 'bangumi_rank_user_settings'; function getUserSettings() { try { const raw = localStorage.getItem(SETTINGS_KEY); const parsed = raw ? JSON.parse(raw) : {}; return { enableBlocking: parsed.enableBlocking ?? true, renumberRanks: parsed.renumberRanks ?? true, }; } catch (e) { return { enableBlocking: true, renumberRanks: true }; } } function setUserSettings(settings) { localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); } function getBlockedMap() { const raw = localStorage.getItem(STORAGE_KEY); return raw ? JSON.parse(raw) : {}; } function setBlockedMap(map) { localStorage.setItem(STORAGE_KEY, JSON.stringify(map)); } function addToBlockedMap(id, name) { const map = getBlockedMap(); if (!(id in map)) { map[id] = name; setBlockedMap(map); } } function removeFromBlockedMap(id) { const map = getBlockedMap(); if (id in map) { delete map[id]; setBlockedMap(map); } } // v1.1 判断排行榜是否开启筛选 function isFiltered() { const pathname = location.pathname; const searchParams = new URLSearchParams(location.search); // 未筛选时 pathname 是 /anime/browser 或 /anime/browser/ const isCleanPath = pathname === '/anime/browser' || pathname === '/anime/browser/'; // 判断是否包含额外的 pathname 参数,如分类、时间 const isPathFiltered = !isCleanPath; // 判断是否含有筛选用的 query,如 orderby const hasFilterParams = searchParams.has('orderby'); return isPathFiltered || hasFilterParams; } // 更改界面,隐藏当前页面中的已屏蔽条目 function blockExistingItems() { const settings = getUserSettings(); if (settings.enableBlocking === false) { return; } const blockedMap = getBlockedMap(); const items = document.querySelectorAll('li[id^="item_"]'); // 页面中存在的条目列表 items.forEach(item => { const idMatch = item.id.match(/^item_(\d+)$/); // 提取出数值ID if (!idMatch) return; // 失败则跳过 const id = idMatch[1]; if (id in blockedMap) { item.remove(); } }); if (!isFiltered() && settings.renumberRanks) { renumberRanks(); } } // v1.1 更改Rank数值,避免页内数字间隔 function renumberRanks() { const items = Array.from(document.querySelectorAll('li[id^="item_"]')); let rankOffset = (function () { // 从 URL 中读取 page 参数,未指定时为第一页 const pageMatch = location.search.match(/[?&]page=(\d+)/); const page = pageMatch ? parseInt(pageMatch[1], 10) : 1; return (page - 1) * 24; })(); items.forEach((item, index) => { const rankElem = item.querySelector('.rank small'); if (rankElem && rankElem.nextSibling?.nodeType === Node.TEXT_NODE) { rankElem.nextSibling.textContent = (rankOffset + index + 1).toString(); } }); } // 显示每个条目的 “屏蔽” 按钮 function showBlockButtons() { const items = document.querySelectorAll('li[id^="item_"]'); items.forEach(item => { const idMatch = item.id.match(/^item_(\d+)$/); if (!idMatch) return; const id = idMatch[1]; const btn = document.createElement('button'); btn.textContent = '×'; btn.className = 'bangumi-block-btn'; Object.assign(btn.style, { position: 'absolute', bottom: '8px', right: '10px', width: '18px', height: '18px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px', padding: '0', background: '#f66', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer', }); btn.onclick = () => { const nameElem = item.querySelector('.inner h3 a'); const name = nameElem?.textContent?.trim() || '未知标题'; addToBlockedMap(id, name); item.remove(); }; item.style.position = 'relative'; item.appendChild(btn); }); } // 隐藏每个条目的 “屏蔽” 按钮 function hideBlockButtons() { const buttons = document.querySelectorAll('.bangumi-block-btn'); buttons.forEach(btn => { btn.parentElement?.removeChild(btn); }); } // 两个控制按键 function createControlButtons() { const toggleBtn = document.createElement('button'); toggleBtn.textContent = '添加屏蔽'; Object.assign(toggleBtn.style, { position: 'absolute', right: '90px', padding: '8px 8px', background: '#F09199', color: '#fff', border: 'none', borderRadius: '12px', cursor: 'pointer', fontSize: '12px', lineHeight: '1', }); toggleBtn.onclick = () => { if (toggleBtn.textContent === '添加屏蔽') { showBlockButtons(); toggleBtn.textContent = '退出'; } else { hideBlockButtons(); toggleBtn.textContent = '添加屏蔽'; } }; const showListBtn = document.createElement('button'); showListBtn.textContent = '管理屏蔽'; Object.assign(showListBtn.style, { position: 'absolute', right: '20px', padding: '8px 8px', background: '#F09199', color: '#fff', border: 'none', borderRadius: '12px', cursor: 'pointer', fontSize: '12px', lineHeight: '1', }); showListBtn.onclick = showControlModal; const header = document.querySelector('#header'); if (header) { header.style.position = 'relative'; header.appendChild(toggleBtn); header.appendChild(showListBtn); } } // 打开管理界面 function showControlModal() { // v1.2 禁止主页面滚动 const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth; const scrollTop = window.scrollY || document.documentElement.scrollTop; document.body.style.position = 'fixed'; document.body.style.top = `-${scrollTop}px`; document.body.style.left = '0'; document.body.style.right = '0'; document.body.style.overflow = 'hidden'; document.body.style.paddingRight = `${scrollBarWidth}px`; // 补偿滚动条宽度 const blocked = getBlockedMap(); const settings = getUserSettings(); // 遮罩层 const overlay = document.createElement('div'); Object.assign(overlay.style, { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.6)', zIndex: 10000, display: 'flex', justifyContent: 'center', alignItems: 'center', }); // v1.2 点击遮罩层关闭 overlay.addEventListener('click', (e) => { if (e.target === overlay) { closeControlModal(overlay, scrollTop); } }); // 粉色主体 // 外层 modal const modalW = document.createElement('div'); Object.assign(modalW.style, { position: 'relative', background: '#F09199', padding: '8px 0px', borderRadius: '10px', width: '300px', maxHeight: '400px', overflow: 'hidden', // 让内部滚动条被裁剪 }); // 内层内容容器 const modal = document.createElement('div'); Object.assign(modal.style, { position: 'relative', padding: '8px 20px', maxHeight: '400px', overflowY: 'auto', // 滚动在内部 boxSizing: 'border-box', scrollbarWidth: 'thin', scrollbarColor: '#ffb6c1 #f09199' // 滑块颜色 背景颜色 }); // 关闭按键 const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; Object.assign(closeBtn.style, { width: '22px', height: '22px', lineHeight: '22px', textAlign: 'center', fontSize: '16px', background: '#ffb6c1', color: '#fff', borderRadius: '50%', border: 'none', cursor: 'pointer', position: 'absolute', top: '13px', right: '14px', transition: 'transform 0.2s ease', }); closeBtn.onmouseover = () => closeBtn.style.transform = 'scale(1.1)'; closeBtn.onmouseout = () => closeBtn.style.transform = 'scale(1)'; closeBtn.onclick = () => closeControlModal(overlay, scrollTop); // ===== 控制区域 设置选项 ===== const title1 = document.createElement('h3'); title1.textContent = `设置选项`; title1.style.marginBottom = '10px'; // v1.2 屏蔽开关 const globalToggleContainer = document.createElement('div'); Object.assign(globalToggleContainer.style, { marginBottom: '10px', fontSize: '13px', display: 'flex', alignItems: 'center', gap: '6px', }); const globalCheckbox = document.createElement('input'); globalCheckbox.type = 'checkbox'; globalCheckbox.checked = settings.enableBlocking ?? true; globalCheckbox.id = 'enable-blocking-toggle'; globalCheckbox.style.cursor = 'pointer'; const globalLabel = document.createElement('label'); globalLabel.textContent = '启用屏蔽功能'; globalLabel.htmlFor = 'enable-blocking-toggle'; globalCheckbox.onchange = () => { settings.enableBlocking = globalCheckbox.checked; setUserSettings(settings); }; globalToggleContainer.appendChild(globalCheckbox); globalToggleContainer.appendChild(globalLabel); // v1.1 重新编号开关 const renumberContainer = document.createElement('div'); Object.assign(renumberContainer.style, { marginBottom: '15px', fontSize: '13px', display: 'flex', alignItems: 'center', gap: '6px', }); const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = getUserSettings().renumberRanks; checkbox.id = 'renumber-toggle'; checkbox.style.cursor = 'pointer'; const label = document.createElement('label'); label.textContent = '重新编号 Rank 排名'; label.htmlFor = 'renumber-toggle'; // label 绑定 input checkbox.onchange = () => { settings.renumberRanks = checkbox.checked; setUserSettings(settings); }; renumberContainer.appendChild(checkbox); renumberContainer.appendChild(label); // ===== 内容区域 屏蔽条目列表 ===== const title2 = document.createElement('h3'); title2.textContent = `已屏蔽${category}条目`; title2.style.marginBottom = '10px'; const list = document.createElement('ul'); // 列表 for (const [id, name] of Object.entries(blocked)) { const li = document.createElement('li'); Object.assign(li.style, { display: 'flex', alignItems: 'center', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', width: '100%', marginBottom: '5px', }); // 创建文本节点(会自动省略过长文本) const textSpan = document.createElement('span'); textSpan.textContent = `${id} - ${name}`; Object.assign(textSpan.style, { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flexGrow: 1, }); const unhideBtn = document.createElement('button'); unhideBtn.textContent = '取消屏蔽'; Object.assign(unhideBtn.style, { marginRight: '8px', padding: '2px 6px', fontSize: '12px', background: '#4CAF50', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer', }); unhideBtn.onclick = () => { removeFromBlockedMap(id); li.remove(); }; li.appendChild(unhideBtn); li.appendChild(textSpan); list.appendChild(li); } // v1.2 清空屏蔽 const clearBtn = document.createElement('button'); clearBtn.textContent = '清空全部屏蔽'; Object.assign(clearBtn.style, { marginTop: '5px', padding: '3px 5px', fontSize: '12px', background: '#d9534f', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer', }); clearBtn.onclick = () => { if (confirm(`确定清空所有已屏蔽${category}条目吗?`)) { setBlockedMap({}); list.innerHTML = ''; } }; modal.appendChild(title1); modal.appendChild(globalToggleContainer); modal.appendChild(renumberContainer); modal.appendChild(title2); modal.appendChild(list); modal.appendChild(clearBtn); modalW.appendChild(modal); modalW.appendChild(closeBtn); overlay.appendChild(modalW); document.body.appendChild(overlay); } // 关闭管理界面 v1.2 function closeControlModal(overlay, scrollTop) { overlay.remove(); document.body.style.position = ''; document.body.style.top = ''; document.body.style.left = ''; document.body.style.right = ''; document.body.style.overflow = ''; document.body.style.paddingRight = ''; if (scrollTop !== undefined) { window.scrollTo(0, scrollTop); } } function init() { blockExistingItems(); createControlButtons(); } window.addEventListener('load', init); const observer = new MutationObserver(() => { blockExistingItems(); }); observer.observe(document.body, { childList: true, subtree: true }); })();