您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
在B站首页、视频页和搜索页添加拉黑按钮,一键拉黑UP主。完整支持BewlyBewly插件首页布局适配。
// ==UserScript== // @name B站一键拉黑UP主 // @description 在B站首页、视频页和搜索页添加拉黑按钮,一键拉黑UP主。完整支持BewlyBewly插件首页布局适配。 // @match https://bilibili.com/ // @match https://www.bilibili.com/* // @match https://www.bilibili.com/video/* // @match https://search.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @version 1.1.9 // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_addStyle // @namespace https://github.com/codertesla/bilibili-1-click-blocker // @author codertesla // @supportURL https://github.com/codertesla/bilibili-1-click-blocker // @homepageURL https://github.com/codertesla/bilibili-1-click-blocker // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @license MIT // ==/UserScript== (function () { 'use strict'; // --- 样式定义 --- const BILI_BLACKLIST_STYLES = ` /* 通用按钮样式 */ .bilibili-blacklist-btn { color: #fb7299 !important; cursor: pointer !important; font-weight: normal !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; padding: 1px 5px !important; border: 1px solid #fb7299 !important; border-radius: 4px !important; font-size: 11px !important; /* 统一基础字号 */ transition: all 0.2s ease !important; background-color: white !important; box-shadow: 0 0 2px rgba(251, 114, 153, 0.2) !important; width: auto !important; min-width: unset !important; max-width: none !important; box-sizing: border-box !important; text-align: center !important; white-space: nowrap !important; gap: 1px !important; vertical-align: middle; /* 垂直对齐 */ line-height: normal; /* 正常行高 */ margin: 0 5px 0 0 !important; /* 默认右边距,给后面元素空间 */ } .bilibili-blacklist-btn:hover { background-color: #fb7299 !important; color: white !important; box-shadow: 0 0 4px rgba(251, 114, 153, 0.4) !important; } .bilibili-blacklist-btn:active { transform: scale(0.95) !important; } .bilibili-blacklist-btn::before { content: "" !important; margin-right: 0 !important; width: 0 !important; } /* 按钮动画 */ @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } .bilibili-blacklist-btn { animation: fadeIn 0.3s ease-out !important; } /* --- 首页特定调整 --- */ /* 将按钮添加到 info--bottom 前面时 */ .bili-video-card__info--bottom > .bilibili-blacklist-btn { /* 首页按钮放在前面,给右边距 */ margin-right: 8px !important; margin-left: 0 !important; } /* --- 视频页特定调整 --- */ /* 将按钮添加到 upname > a > span.name 后面时 */ .upname a > .bilibili-blacklist-btn { margin-left: 5px !important; /* 视频页按钮放名字后面,给左边距 */ margin-right: 0 !important; font-size: 10px !important; /* 视频页按钮可以小一点 */ padding: 0px 4px !important; } /* --- 卡片悬浮按钮特定调整 --- */ .blacklist-button-container { position: absolute !important; top: 5px !important; right: 5px !important; z-index: 100 !important; opacity: 0 !important; transition: opacity 0.2s ease !important; } .bili-video-card:hover .blacklist-button-container, .video-card:hover .blacklist-button-container, .feed-card:hover .blacklist-button-container { opacity: 1 !important; } .blacklist-button-container .bilibili-blacklist-btn { padding: 0px 4px !important; font-size: 10px !important; min-width: unset !important; max-width: none !important; white-space: nowrap !important; margin: 0 !important; /* 悬浮按钮不需要外边距 */ } /* --- Toast 提示样式 (v1.1.9 优化) --- */ .bili-blacklist-toast { position: fixed !important; z-index: 99999 !important; top: 30px !important; left: 50% !important; transform: translateX(-50%) translateY(0) !important; background-color: rgba(0, 0, 0, 0.8) !important; color: white !important; padding: 10px 20px !important; border-radius: 8px !important; font-size: 14px !important; display: flex !important; align-items: center !important; gap: 10px !important; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; transition: opacity 0.3s ease, transform 0.3s ease !important; opacity: 1 !important; animation: toastIn 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) !important; } @keyframes toastIn { from { opacity: 0; transform: translateX(-50%) translateY(-20px) !important; } to { opacity: 1; transform: translateX(-50%) translateY(0) !important; } } .bili-blacklist-toast-icon { font-size: 18px !important; line-height: 1 !important; } .bili-blacklist-toast-content { line-height: 1.5 !important; } /* --- 新增:插件修改布局的适配样式 --- */ /* 适配 group/desc 布局中的 channel-name 按钮 */ .group\\/desc .channel-name + .bilibili-blacklist-btn { margin-left: 8px !important; margin-right: 0 !important; font-size: 10px !important; padding: 1px 4px !important; vertical-align: middle !important; } /* 适配插件修改布局的悬浮按钮容器 */ .group\\/desc .blacklist-button-container { position: absolute !important; top: 8px !important; right: 8px !important; z-index: 100 !important; opacity: 0 !important; transition: opacity 0.2s ease !important; } .group\\/desc:hover .blacklist-button-container { opacity: 1 !important; } `; // 调试功能 const DEBUG = true; function log(...args) { if (DEBUG) { console.log('[拉黑脚本]', ...args); } } // 等待jQuery加载 function waitForJQuery(callback) { if (typeof jQuery !== 'undefined') { log('jQuery已加载'); callback(jQuery); } else { log('等待jQuery加载...'); setTimeout(function () { waitForJQuery(callback); }, 50); } } // --- Debounce 函数 --- function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // --- // 主函数 waitForJQuery(function ($) { log('脚本初始化开始 v1.1.9-toast-fix'); // --- 样式注入 --- // 1. 注入到主文档 if (typeof GM_addStyle !== 'undefined') { GM_addStyle(BILI_BLACKLIST_STYLES); } // 2. 注入到 Shadow DOM const injectedShadowHosts = new WeakSet(); function injectStylesIntoShadowDOMs(css) { document.querySelectorAll('*').forEach(el => { if (el.shadowRoot && !injectedShadowHosts.has(el)) { const style = document.createElement('style'); style.textContent = css; el.shadowRoot.appendChild(style); injectedShadowHosts.add(el); log(`[Shadow DOM] Styles injected into:`, el); } }); } // --- // Cookie获取函数 (保持不变) function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); return ''; } // 检查是否登录 (保持不变) const csrf = getCookie('bili_jct'); if (!csrf) { log('警告: 未获取到bili_jct Cookie,可能未登录'); showToast('B站拉黑脚本提示:请先登录B站账号!', 5000); } else { log('成功获取CSRF token'); } // 菜单控制 (保持不变) const menuctl = ({ initValue = 0 }) => { let total = initValue; let menuId = null; const currentName = () => "去管理黑名单 --( " + (total < 1 ? "请留意黑名单数量 )" : `总共:${total} )`); const register = () => { try { menuId = GM_registerMenuCommand(currentName(), () => { window.open('https://account.bilibili.com/account/blacklist', '_blank'); }); log('注册菜单成功: ', currentName()); } catch (e) { log('注册菜单失败: ', e); } }; register(); const ctl = { get total() { return total; }, set total(newValue) { if (newValue == total) return; if (menuId !== null) { try { GM_unregisterMenuCommand(menuId); log('解除注册旧菜单'); } catch (e) { log('解除注册旧菜单失败: ', e); } } total = newValue; register(); }, }; return ctl; }; const menu = menuctl({ initValue: 0 }); // 显示自定义提示框 (保持不变) function showToast(message, duration = 3000) { $('.bili-blacklist-toast').remove(); const toast = $(`<div class="bili-blacklist-toast"><span class="bili-blacklist-toast-icon">✓</span><div class="bili-blacklist-toast-content">${message}</div></div>`); $('body').append(toast); setTimeout(() => { toast.css({ 'opacity': '0', 'transform': 'translateX(-50%) translateY(-20px)' }); setTimeout(() => toast.remove(), 300); // 等待动画完成再移除 }, duration); } // 拉黑功能 (保持不变) // === 更新后的 window.tools_toblack 函数 === window.tools_toblack = (uid, upName) => { log('执行拉黑操作,UID:', uid, '名称:', upName); const isVideoPage = window.location.href.includes('/video/'); // --- 辅助函数:更新按钮状态为“已拉黑” --- const setButtonToBlocked = (targetUid) => { log(`准备将 UID ${targetUid} 的按钮状态更新为 '已拉黑'`); const selector = `.bilibili-blacklist-btn[data-uid="${targetUid}"]:not(:disabled)`; // 使用 findInShadowDOM 查找按钮,以兼容所有情况 findInShadowDOM(selector).each(function () { const $button = $(this); if ($button.is(':visible')) { log('找到按钮并更新为已拉黑:', $button[0]); $button.text('已拉黑').css({ 'opacity': '0.6', 'cursor': 'not-allowed', 'background-color': '#eee', 'border-color': '#ddd', 'color': '#aaa' }).prop('disabled', true).off('click'); } }); }; // --- 辅助函数结束 --- // 执行 API 请求 fetch("https://api.bilibili.com/x/relation/modify", { method: "POST", credentials: 'include', headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ 'fid': uid, 'act': 5, 're_src': 11, 'gaia_source': 'web_main', 'csrf': getCookie('bili_jct'), }) }).then(res => { if (!res.ok) { throw new Error(`HTTP error! Status: ${res.status}`); } return res.json(); }).then(data => { log('拉黑API响应:', data); if (data.code === 0) { // 拉黑成功 log('拉黑成功:', uid); showToast(`已成功将 "${upName || 'UP主'}" 加入黑名单`); setButtonToBlocked(uid); // 调用辅助函数更新按钮状态 if (!isVideoPage) { // 首页额外操作:移除卡片 log('执行首页移除操作...'); // 同时在主文档和 Shadow DOM 中查找并移除卡片 const cardSelectors = [ `.bili-video-card[data-up-id="${uid}"]`, `.feed-card[data-up-id="${uid}"]`, `.video-card[data-up-id="${uid}"]`, `.group\\/desc[data-up-id="${uid}"]`, `div.uid_${uid}` ].join(', '); findInShadowDOM(cardSelectors).fadeOut(300, function () { $(this).remove(); }); } } else { // 拉黑失败 (API返回错误码) log('拉黑失败:', data.message || `错误码 ${data.code}`); // === 新增:检查是否是 "已拉黑" 错误码 === if (data.code === 22120) { log('检测到错误码 22120 (用户已被拉黑)'); showToast('该用户已被拉黑'); // 显示更具体的提示 setButtonToBlocked(uid); // 同样调用辅助函数更新按钮状态 } else { // 对于其他所有错误,只显示通用失败提示,不改变按钮状态 showToast(`拉黑失败: ${data.message || `错误码 ${data.code}`}`); } // === 检查结束 === } }).catch(err => { // 网络请求错误等 log('拉黑请求错误:', err); showToast('拉黑请求失败,请检查网络或登录状态'); // 网络错误不改变按钮状态 }); updateBlacklistCount(); // 更新黑名单计数 }; // 更新黑名单计数 (保持不变) function updateBlacklistCount() { fetch("https://api.bilibili.com/x/relation/blacks?re_version=0&pn=1&ps=20&jsonp=jsonp&web_location=333.33", { method: "GET", credentials: 'include', }).then(res => { if (!res.ok) { throw new Error(`HTTP error! Status: ${res.status}`); } return res.json(); }).then(data => { log('黑名单API响应:', data); if (data.code === 0) { menu.total = data.data.total; log('更新黑名单计数:', data.data.total); } else { log('获取黑名单失败:', data.message || '未知错误'); } }).catch(err => { log('获取黑名单请求错误:', err); }); } // --- 新增:Shadow DOM 搜索函数 --- function findInShadowDOM(selector) { let results = $(selector); // 遍历所有元素,检查是否有 shadowRoot log(' [Shadow DOM] 开始搜索...'); $('*').each(function (index, el) { if (this.shadowRoot) { log(` [Shadow DOM] 在元素 ${el.tagName}.${el.className} 中找到 shadowRoot`); const shadowResults = $(this.shadowRoot).find(selector); if (shadowResults.length > 0) { log(` [Shadow DOM] 找到 ${shadowResults.length} 个匹配 "${selector}" 的元素`); results = results.add(shadowResults); } } }); log(` [Shadow DOM] 搜索结束,共找到 ${results.length} 个结果`); return results; } // === 处理首页 (已修改,新增插件布局适配) === function processHomePage() { log('处理首页'); // 全局调试:检查页面基本状态 log('=== 页面基本状态 ==='); log(`jQuery版本: ${$.fn.jquery}, 页面URL: ${window.location.href}`); log(`页面title: ${document.title}`); log(`DOM中总div数量: ${$('div').length}`); log(`包含data-v-属性的元素数量: ${$('[data-v-89bbbbc2]').length}`); log(`包含class="video-card"的元素数量: ${$('.video-card').length}`); log(`包含class包含"video"的元素数量: ${$('[class*="video"]').length}`); const possibleContainers = ['.bili-video-card', '.video-card', '.bili-video-card__wrap', '.feed-card', '.group\\/desc']; let foundCards = false; // 调试:检查页面中存在哪些可能的容器 log('=== 首页调试信息 ==='); possibleContainers.forEach(selector => { const allElements = $(selector); const unprocessedElements = $(`${selector}:not([data-toblack-processed="true"])`); log(`容器 ${selector}: 总数=${allElements.length}, 未处理=${unprocessedElements.length}`); // 如果是.video-card,显示更多调试信息 if (selector === '.video-card' && allElements.length > 0) { log(' .video-card示例:'); allElements.slice(0, 2).each((i, el) => { const $el = $(el); log(` 示例${i + 1}: class="${$el.attr('class')}", 内部是否有channel-name: ${$el.find('.channel-name').length}`); const channelLinks = $el.find('.channel-name'); if (channelLinks.length > 0) { channelLinks.each((j, link) => { log(` channel-name ${j + 1}: href="${$(link).attr('href')}", text="${$(link).text().trim()}"`); }); } }); } }); // 额外调试:检查是否有其他可能的BewlyBewly容器 const bewlyContainerSelectors = [ '.video-card', '.video-card.group', '[class*="video-card"]' ]; log('=== BewlyBewly容器检查 ==='); bewlyContainerSelectors.forEach(selector => { const elements = $(selector); if (elements.length > 0) { log(`可能的BewlyBewly容器 ${selector}: ${elements.length}个`); // 只显示前3个元素的类名作为参考 elements.slice(0, 3).each((i, el) => { log(` 示例${i + 1}: class="${$(el).attr('class')}" tag="${el.tagName}"`); }); } }); possibleContainers.forEach(containerSelector => { const cards = findInShadowDOM(`${containerSelector}:not([data-toblack-processed="true"])`); if (cards.length > 0) { // log(`找到 ${cards.length} 个未处理的视频卡片 (${containerSelector})`); // 减少日志 foundCards = true; cards.each((index, card) => { const $card = $(card); // 标记立即处理,避免重复 $card.attr('data-toblack-processed', 'true'); let ownerLinkElement = null; let upName = ''; let ownerUrl = ''; let uid = ''; // 调试:记录正在处理的卡片信息 log(`正在处理卡片 (${containerSelector}):`, $card[0]); // 优先使用新结构选择器 ownerLinkElement = $card.find('a.bili-video-card__info--owner'); if (ownerLinkElement.length > 0) { ownerUrl = ownerLinkElement.attr('href'); const authorSpan = ownerLinkElement.find('span.bili-video-card__info--author'); if (authorSpan.length > 0) { upName = authorSpan.attr('title') || authorSpan.text().trim(); // 优先用 title } else { // 备选:直接取链接文本,尝试去除日期 upName = ownerLinkElement.text().trim().split('·')[0].trim(); } log(`首页: 结构匹配成功 (a.bili-video-card__info--owner)`); } else { // 新增:检查插件修改布局中的 channel-name 结构 const channelNameElement = $card.find('a.channel-name'); if (channelNameElement.length > 0) { ownerLinkElement = channelNameElement; ownerUrl = channelNameElement.attr('href'); // 在 channel-name 中提取名称,可能在嵌套的 span 中 const nameSpans = channelNameElement.find('span span'); if (nameSpans.length > 0) { upName = nameSpans.last().text().trim(); } else { upName = channelNameElement.text().trim(); } log(`首页: 插件布局结构匹配成功 (a.channel-name)`); } else { // Fallback 到旧的选择器逻辑 const possibleOwnerSelectors = ['.up-name', '.author-text', '.up-name__text']; for (const selector of possibleOwnerSelectors) { const element = $card.find(selector); if (element.length > 0) { ownerLinkElement = element; // 记录找到的元素 ownerUrl = element.attr('href'); upName = element.text().trim(); log(`首页: 备选结构匹配成功 (${selector})`); break; } } } } if (!ownerUrl || !ownerLinkElement) { log('❌ 未能找到卡片上的UP主链接,调试信息:'); log('- ownerUrl:', ownerUrl); log('- ownerLinkElement:', ownerLinkElement); log('- 卡片HTML结构:', $card[0].outerHTML.substring(0, 500) + '...'); // 额外调试:尝试找到卡片中所有的链接 const allLinks = $card.find('a[href*="space"]'); log('- 卡片中所有包含"space"的链接数量:', allLinks.length); allLinks.each((i, link) => { log(` 链接${i + 1}: href="${$(link).attr('href')}", text="${$(link).text().trim()}", class="${$(link).attr('class')}"`); }); return; // Skip card } // 提取 UID (新增对 //space.bilibili.com/ 格式的支持) if (ownerUrl.includes('/space.bilibili.com/')) { uid = ownerUrl.split('/space.bilibili.com/')[1].split('?')[0].split('/')[0]; } else if (ownerUrl.includes('//space.bilibili.com/')) { // 处理插件修改布局中的 //space.bilibili.com/ 格式 uid = ownerUrl.split('//space.bilibili.com/')[1].split('?')[0].split('/')[0]; } else if (ownerUrl.includes('/space/')) { uid = ownerUrl.split('/space/')[1].split('?')[0].split('/')[0]; } else { const match = ownerUrl.match(/\/(\d+)(\/|\?|$)/); if (match && match[1]) { uid = match[1]; } } if (!uid || !/^\d+$/.test(uid)) { // log('无法从URL提取有效的首页UID:', ownerUrl); return; // Skip card } // 给卡片添加 data-up-id 属性,方便拉黑后移除 $card.attr('data-up-id', uid); if (!upName) { upName = `UID: ${uid}`; } // Default name if extraction failed log('提取首页UID:', uid, '名称:', upName); // --- 按钮放置 --- let buttonAdded = false; // 新增:优先尝试插件布局的 channel-name 后面 if (!buttonAdded && ownerLinkElement && ownerLinkElement.hasClass('channel-name')) { if (ownerLinkElement.next('.bilibili-blacklist-btn').length === 0) { const blackButton = $(`<a class="bilibili-blacklist-btn" data-uid="${uid}">拉黑</a>`); blackButton.on('click', function (e) { e.preventDefault(); e.stopPropagation(); window.tools_toblack(uid, upName); }); ownerLinkElement.after(blackButton); buttonAdded = true; log('按钮添加到 channel-name 后面'); } else { buttonAdded = true; /* Already exists */ } } // 原有逻辑:尝试添加到 info--bottom 的前面 if (!buttonAdded) { const bottomInfoDiv = $card.find('.bili-video-card__info--bottom'); if (bottomInfoDiv.length > 0) { if (bottomInfoDiv.find('.bilibili-blacklist-btn').length === 0) { // 避免重复添加 const blackButton = $(`<a class="bilibili-blacklist-btn" data-uid="${uid}">拉黑</a>`); blackButton.on('click', function (e) { e.preventDefault(); e.stopPropagation(); window.tools_toblack(uid, upName); }); bottomInfoDiv.prepend(blackButton); buttonAdded = true; log('按钮添加到 info--bottom 前面'); } else { buttonAdded = true; /* Already exists */ } } } // 备选:添加到卡片右上角悬浮 (如果上面没成功) if (!buttonAdded && $card.find('.blacklist-button-container').length === 0) { if (!$card.css('position') || $card.css('position') === 'static') { $card.css('position', 'relative'); //确保卡片有定位上下文 } const container = $('<div class="blacklist-button-container"></div>'); const blackButton = $(`<a class="bilibili-blacklist-btn" data-uid="${uid}">拉黑</a>`); blackButton.on('click', function (e) { e.preventDefault(); e.stopPropagation(); window.tools_toblack(uid, upName); }); container.append(blackButton); $card.append(container); buttonAdded = true; log('按钮添加到悬浮容器'); } // 旧的 UID class 逻辑,保留 $card.addClass('uid_' + uid); }); } }); // if (!foundCards) { log('本次未找到任何需要处理的视频卡片'); } // 减少日志 } // === 处理视频页面 (保持不变, 使用 v1.0.7 的逻辑) === function processVideoPage() { log('处理视频页面'); function findAndProcessUpInfo() { // Target the container div first, ensure it's not already processed const upnameDivs = $('div.upname:not([data-toblack-processed="true"])'); if (upnameDivs.length > 0) { log(`找到 ${upnameDivs.length} 个未处理的视频页UP主容器 (div.upname)`); upnameDivs.each(function () { const upnameDiv = $(this); // Find the main link (<a>) inside the div, which contains the space URL const linkElement = upnameDiv.find('a[href*="/space.bilibili.com/"], a[href*="/space/"]'); // Find the name span (span.name) inside the link const nameElement = linkElement.find('span.name'); if (linkElement.length > 0 && nameElement.length > 0) { if (linkElement.find('.bilibili-blacklist-btn').length > 0) { upnameDiv.attr('data-toblack-processed', 'true'); // log('按钮已存在于 upname link 中,跳过'); //减少日志 return; } const upUrl = linkElement.attr('href'); const upName = nameElement.text().trim(); if (!upUrl || !upName) { upnameDiv.attr('data-toblack-processed', 'true'); return; } let uid = ''; if (upUrl.includes('/space.bilibili.com/')) { uid = upUrl.split('/space.bilibili.com/')[1].split('?')[0].split('/')[0]; } else if (upUrl.includes('/space/')) { uid = upUrl.split('/space/')[1].split('?')[0].split('/')[0]; } else { const match = upUrl.match(/\/(\d+)(\/|\?|$)/); if (match && match[1]) { uid = match[1]; } } if (!uid || !/^\d+$/.test(uid)) { upnameDiv.attr('data-toblack-processed', 'true'); return; } // ... (code to extract uid and upName) ... log('提取视频页UID:', uid, '名称:', upName); const blackButton = $(`<a class="bilibili-blacklist-btn" data-uid="${uid}">拉黑</a>`); // === MODIFIED CLICK HANDLER for Video Page === blackButton.on('click', function (e) { e.preventDefault(); e.stopPropagation(); console.log('[拉黑脚本] --- 视频页按钮点击事件触发 ---'); // Log: Handler Fired const buttonElement = $(this); // Re-read UID from the button's data attribute at the time of click const clickedUid = buttonElement.data('uid'); // Use the name captured when the button was created (closure) // Alternatively, could try finding the name span again relative to buttonElement if needed const clickedName = upName; console.log('[拉黑脚本] 点击时获取的 UID:', clickedUid, typeof clickedUid); console.log('[拉黑脚本] 点击时获取的 Name:', clickedName); // Validate the UID before calling the API function if (!clickedUid || typeof clickedUid === 'undefined' || String(clickedUid).trim() === '' || !/^\d+$/.test(String(clickedUid))) { console.error('[拉黑脚本] 错误:点击时 UID 无效!', clickedUid); showToast('拉黑失败:无法获取有效的 UP 主 ID'); // Optionally add more specific error messages based on the condition // if (!clickedUid) { showToast('拉黑失败:UID 未定义'); } // else if (!/^\d+$/.test(String(clickedUid))) { showToast('拉黑失败:UID 非数字'); } return; // Stop if UID is invalid } // If UID is valid, proceed to call the block function console.log('[拉黑脚本] UID 有效,准备调用 tools_toblack...'); try { window.tools_toblack(String(clickedUid), clickedName); } catch (apiError) { console.error('[拉黑脚本] 调用 tools_toblack 时出错:', apiError); showToast('拉黑操作内部出错,请检查控制台'); } }); // === END MODIFIED CLICK HANDLER === nameElement.after(blackButton); // Insert button after the name span upnameDiv.attr('data-toblack-processed', 'true'); log('已添加拉黑按钮到视频页UP主:', upName); } else { upnameDiv.attr('data-toblack-processed', 'true'); } }); return true; // Found and processed elements } return false; // Did not find any new div.upname to process this time } // Retry logic (保持不变) if (!findAndProcessUpInfo()) { // log('首次未找到UP主信息 (div.upname),将在稍后重试'); // Reduce log noise let retryCount = 0; const maxRetries = 5; const retryInterval = 800; if (window.videoPageRetryTimer) { clearInterval(window.videoPageRetryTimer); } window.videoPageRetryTimer = setInterval(() => { retryCount++; if (findAndProcessUpInfo() || retryCount >= maxRetries) { clearInterval(window.videoPageRetryTimer); window.videoPageRetryTimer = null; if (retryCount >= maxRetries && !$('div.upname .bilibili-blacklist-btn').length) { log('在多次尝试后仍未找到或添加按钮到 UP主信息 (div.upname)'); } else if ($('div.upname .bilibili-blacklist-btn').length > 0) { log(`通过重试找到并处理了 UP 主信息`); } } }, retryInterval); } } // 统一处理入口 (保持不变) function processPage() { if (window.processingPage) { return; } // 简化跳过逻辑 window.processingPage = true; try { const isVideoPage = window.location.href.includes('/video/'); if (isVideoPage) { processVideoPage(); } else { processHomePage(); } } catch (error) { log("处理页面时出错:", error); } finally { // 使用 setTimeout 确保 processingPage 标志在稍后重置 setTimeout(() => { window.processingPage = false; }, 300); // 减少延迟 } } // === 设置DOM观察器 (已修改首页目标) === function setupObserver() { if (window.blacklistObserverSet) { log('观察器已存在,跳过设置'); return; } log('设置DOM观察器'); // --- Debounce 处理函数 --- const debouncedProcessPage = debounce(processPage, 300); // --- const observer = new MutationObserver(function (mutations) { // 每当DOM变化,都尝试注入样式,并重新处理页面 injectStylesIntoShadowDOMs(BILI_BLACKLIST_STYLES); debouncedProcessPage(); }); const isVideoPage = window.location.href.includes('/video/'); const isSearchPage = window.location.href.includes('search.bilibili.com'); let targetNode = null; if (isVideoPage) { // 视频页目标 (保持 v1.0.7 的选择) targetNode = document.querySelector('#viewbox_report') || document.querySelector('.video-info-detail') || document.querySelector('#app .left-container'); log('尝试为视频页选择观察节点:', targetNode ? (targetNode.id || targetNode.className) : '未找到特定节点'); } else if (isSearchPage) { // 搜索页面目标: 添加搜索结果特定的选择器 targetNode = document.querySelector('.video-list') || // 搜索结果视频列表 document.querySelector('.search-content') || // 搜索内容区域 document.querySelector('.search-page') || // 搜索页面容器 document.querySelector('#app .bili-grid') || // 通用网格布局 document.querySelector('#app .bili-layout') || // 更通用的布局容器 document.querySelector('#app'); // 万不得已才用 #app log('尝试为搜索页面选择观察节点:', targetNode ? (targetNode.id || targetNode.className) : '未找到特定节点'); } else { // 首页目标: 优先尝试包含BewlyBewly video-card的容器 targetNode = document.querySelector('[data-v-89bbbbc2]') || // BewlyBewly的数据属性容器 document.querySelector('#app .feed-list') || // 推荐流 document.querySelector('#i_cecream') || // 首页外层容器 ID 之一 document.querySelector('.bili-grid') || // 通用网格布局 document.querySelector('#app .bili-layout') || // 更通用的布局容器 document.querySelector('#app'); // 万不得已才用 #app log('尝试为首页选择观察节点:', targetNode ? (targetNode.id || targetNode.className || targetNode.tagName) : '未找到特定节点'); } // 最终备选:如果找不到任何特定节点,就观察body if (!targetNode) { targetNode = document.body; log('警告:未找到特定观察节点,将观察整个 body'); } if (targetNode) { log('最终选择观察节点:', targetNode); observer.observe(targetNode, { childList: true, subtree: true }); window.blacklistObserverSet = true; log('DOM观察器已启动'); } else { log('警告:未能找到合适的DOM节点进行观察,MutationObserver 未启动。按钮可能只在初始加载时添加。'); } // 初始加载时检查 (总会执行一次) injectStylesIntoShadowDOMs(BILI_BLACKLIST_STYLES); // 首次注入 setTimeout(processPage, 1500); // BewlyBewly可能需要更长加载时间,增加延迟重试 setTimeout(() => { log('=== 延迟重试检查 (为BewlyBewly) ==='); processPage(); }, 3000); setTimeout(() => { log('=== 最后一次重试检查 ==='); processPage(); }, 5000); // 定期检查作为后备 (可选,可以注释掉) // if (window.blacklistInterval) clearInterval(window.blacklistInterval); // window.blacklistInterval = setInterval(processPage, 10000); } // 暴露给全局作用域 (保持不变) window.blacklistScript = { processPage, processHomePage, processVideoPage }; // 启动逻辑 (保持不变) $(document).ready(function () { log('页面就绪,初始化脚本'); setupObserver(); updateBlacklistCount(); }); // 后备启动逻辑 (保持不变) setTimeout(function () { if (!window.blacklistObserverSet && !$('body').data('blacklist-init-fallback')) { log('延时后备初始化'); $('body').data('blacklist-init-fallback', true); setupObserver(); updateBlacklistCount(); processPage(); } }, 3000); log('脚本初始化完成'); }); })();