// ==UserScript== // @name B站工具箱:消息图标开关 + 点赞详情右侧抽屉 // @namespace http://tampermonkey.net/ // @version 4.0 // @description 1. 一键显示/隐藏B站顶栏消息通知图标(无缝排版);2. 消息中心“收到的赞”支持右侧滑出抽屉查看详情,完美保留列表滚动位置。 // @author Tomato Flurry // @license MIT // @match *://*.bilibili.com/* // @match *://bilibili.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @run-at document-start // ==/UserScript== /* ========================================================================= * 模块一:顶栏消息通知一键开关 (无缝排版版) * ========================================================================= */ (function initMessageToggleModule() { 'use strict'; // 默认状态:false 为隐藏消息,true 为显示消息 let showMessage = GM_getValue('show_bili_message', false); const STYLE_ID = 'bili-mess-tight-style'; // 1. 深度隐藏样式:连同整个 li / 菜单项项容器一并从 Flex 布局中完全剔除 const hideCss = ` /* 1. 现代顶栏直接容器(通常是 ul 下的 li 或 entry-item) */ .right-entry__outside:has(a[href*="//message.bilibili.com"]), .right-entry-item:has(a[href*="//message.bilibili.com"]), .header-entry-mini:has(a[href*="//message.bilibili.com"]), .v-popover-wrap:has(a[href*="//message.bilibili.com"]), li:has(a[href*="//message.bilibili.com"]), /* 兼容旧版及特化页面 */ .right-entry--message, /* 兜底:直接匹配消息链接本身 */ a[href*="//message.bilibili.com"] { display: none !important; visibility: hidden !important; pointer-events: none !important; width: 0 !important; min-width: 0 !important; max-width: 0 !important; margin: 0 !important; margin-left: 0 !important; margin-right: 0 !important; padding: 0 !important; border: none !important; flex: 0 0 0 !important; } `; function updateStyle() { let styleEl = document.getElementById(STYLE_ID); if (!showMessage) { if (!styleEl) { styleEl = document.createElement('style'); styleEl.id = STYLE_ID; styleEl.textContent = hideCss; (document.head || document.documentElement).appendChild(styleEl); } cleanUpDomHolders(true); } else { if (styleEl) { styleEl.remove(); } cleanUpDomHolders(false); } } // 2. JS 兜底处理:防止某些旧浏览器不支持 :has() 导致的外层父容器留白 function cleanUpDomHolders(hide) { const link = document.querySelector('a[href*="//message.bilibili.com"]'); if (!link) return; let target = link.parentElement; while (target && target !== document.body) { const tag = target.tagName.toLowerCase(); const cls = target.className || ''; if (tag === 'li' || cls.includes('entry') || cls.includes('popover')) { break; } target = target.parentElement; } if (target && target !== document.body) { if (hide) { target.style.setProperty('display', 'none', 'important'); target.style.setProperty('margin', '0', 'important'); target.style.setProperty('padding', '0', 'important'); } else { target.style.removeProperty('display'); target.style.removeProperty('margin'); target.style.removeProperty('padding'); } } } // 初始化样式 updateStyle(); // 3. 状态切换 function toggleState(newVal) { showMessage = typeof newVal === 'boolean' ? newVal : !showMessage; GM_setValue('show_bili_message', showMessage); updateStyle(); updateButtonUI(); } // 4. 油猴扩展菜单 function registerMenu() { const text = showMessage ? '🔔 消息图标:已开启 (点击关闭)' : '🔕 消息图标:已关闭 (点击开启)'; GM_registerMenuCommand(text, () => { toggleState(); location.reload(); }); } registerMenu(); // 5. 悬浮切换按钮 function createToggleButton() { if (document.getElementById('bili-mess-toggle-btn')) return; const btn = document.createElement('div'); btn.id = 'bili-mess-toggle-btn'; btn.style.cssText = ` position: fixed; bottom: 85px; right: 18px; z-index: 99999; width: 36px; height: 36px; border-radius: 50%; background: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 18px; transition: all 0.2s ease; user-select: none; `; btn.addEventListener('mouseenter', () => btn.style.transform = 'scale(1.1)'); btn.addEventListener('mouseleave', () => btn.style.transform = 'scale(1)'); btn.addEventListener('click', () => toggleState()); document.body.appendChild(btn); updateButtonUI(); } function updateButtonUI() { const btn = document.getElementById('bili-mess-toggle-btn'); if (!btn) return; if (showMessage) { btn.textContent = '🔔'; btn.title = '当前:消息可见(点击隐藏)'; btn.style.opacity = '0.9'; btn.style.border = '1px solid #00aeec'; } else { btn.textContent = '🔕'; btn.title = '当前:消息已隐藏(点击显示)'; btn.style.opacity = '0.5'; btn.style.border = '1px solid #ddd'; } } // 页面挂载与渲染监听 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { createToggleButton(); updateStyle(); }); } else { createToggleButton(); updateStyle(); } const observer = new MutationObserver(() => { if (!showMessage) { cleanUpDomHolders(true); } }); observer.observe(document.documentElement, { childList: true, subtree: true }); })(); /* ========================================================================= * 模块二:消息中心“收到的赞”右侧抽屉详情 (位置保持版) * ========================================================================= */ (function initLikeDetailDrawerModule() { 'use strict'; // 全局状态控制 let currentCardId = null; let currentPage = 1; let isEnd = false; let isLoading = false; let loadedUserMids = new Set(); let mask = null; let drawerBody = null; let drawerInfo = null; let drawerList = null; let drawerLoading = null; // 1. 创建并注入抽屉 UI 组件 function initDrawerUI() { if (document.getElementById('bili-like-drawer-mask')) return; const style = document.createElement('style'); style.innerHTML = ` .bili-drawer-mask { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.3); z-index: 99999; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; } .bili-drawer-mask.active { opacity: 1; pointer-events: auto; } .bili-drawer-content { position: fixed; top: 0; right: -420px; width: 400px; height: 100vh; background: #ffffff; box-shadow: -4px 0 16px rgba(0,0,0,0.15); z-index: 100000; transition: right 0.3s cubic-bezier(0.16, 1, 0.3, 1); display: flex; flex-direction: column; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; } .bili-drawer-mask.active .bili-drawer-content { right: 0; } .bili-drawer-header { padding: 16px 20px; border-bottom: 1px solid #e3e5e7; display: flex; justify-content: space-between; align-items: center; } .bili-drawer-title { font-size: 16px; font-weight: 600; color: #18191c; } .bili-drawer-close { cursor: pointer; font-size: 20px; color: #9499a0; line-height: 1; } .bili-drawer-close:hover { color: #00aeec; } .bili-drawer-body { flex: 1; overflow-y: auto; padding: 16px 20px; } .bili-drawer-card-info { background: #f1f2f3; padding: 12px; border-radius: 8px; margin-bottom: 16px; } .bili-drawer-card-title { font-size: 14px; font-weight: 500; color: #18191c; margin-bottom: 6px; } .bili-drawer-card-link { font-size: 13px; color: #00aeec; text-decoration: none; word-break: break-all; } .bili-drawer-card-link:hover { text-decoration: underline; } .bili-drawer-user-item { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #f1f2f3; } .bili-drawer-user-avatar { width: 40px; height: 40px; border-radius: 50%; margin-right: 12px; } .bili-drawer-user-name { font-size: 14px; color: #18191c; font-weight: 500; } .bili-drawer-user-time { font-size: 12px; color: #9499a0; margin-top: 2px; } .bili-drawer-loading-tip { text-align: center; padding: 12px 0; color: #9499a0; font-size: 13px; } `; (document.head || document.documentElement).appendChild(style); mask = document.createElement('div'); mask.id = 'bili-like-drawer-mask'; mask.className = 'bili-drawer-mask'; mask.innerHTML = `
点赞详情
`; document.body.appendChild(mask); const closeBtn = mask.querySelector('.bili-drawer-close'); drawerBody = mask.querySelector('#bili-drawer-body'); drawerInfo = mask.querySelector('#bili-drawer-info'); drawerList = mask.querySelector('#bili-drawer-list'); drawerLoading = mask.querySelector('#bili-drawer-loading'); const closeDrawer = () => mask.classList.remove('active'); closeBtn.addEventListener('click', closeDrawer); mask.addEventListener('click', (e) => { if (e.target === mask) closeDrawer(); }); drawerBody.addEventListener('scroll', () => { if (isEnd || isLoading || !currentCardId) return; if (drawerBody.scrollTop + drawerBody.clientHeight >= drawerBody.scrollHeight - 80) { loadNextPage(); } }); } function formatTime(timestamp) { if (!timestamp) return ''; return new Date(timestamp * 1000).toLocaleString(); } function getCardIdFromElement(el) { if (!el) return null; const vueKeys = Object.keys(el).filter(k => k.startsWith('__vue__') || k.startsWith('__vueParentProcess')); for (let key of vueKeys) { const vm = el[key]; if (vm) { const cardData = vm.item || vm.card || vm.data || (vm.setupState && vm.setupState.item); if (cardData && (cardData.id || cardData.card_id)) { return cardData.id || cardData.card_id; } } } return null; } function loadNextPage() { if (isLoading || isEnd) return; isLoading = true; drawerLoading.innerText = '正在加载更多点赞用户...'; fetch(`https://api.bilibili.com/x/msgfeed/like_detail?card_id=${currentCardId}&pn=${currentPage}`, { credentials: 'include' }) .then(res => res.json()) .then(data => { if (!data || data.code !== 0) { drawerLoading.innerText = '加载失败,请重试'; isLoading = false; return; } const card = data.data.card || {}; const items = data.data.items || []; const page = data.data.page || {}; if (currentPage === 1) { drawerInfo.innerHTML = `
目标:${card.title || '点赞详情'} (${card.business || '评论/弹幕'})
${card.uri ? `点击在前台打开对应视频/评论 ↗` : ''}
`; } let newHtml = ''; let validCount = 0; items.forEach(item => { const user = item.user || {}; const userMid = user.mid || (user.nickname + item.like_time); if (!loadedUserMids.has(userMid)) { loadedUserMids.add(userMid); validCount++; newHtml += `
${user.nickname || '匿名用户'}
${formatTime(item.like_time)}
`; } }); if (newHtml) { drawerList.insertAdjacentHTML('beforeend', newHtml); } if (page.is_end || items.length === 0 || (items.length > 0 && validCount === 0 && currentPage > 1)) { isEnd = true; drawerLoading.innerText = `已加载全部点赞用户 (共 ${loadedUserMids.size} 人)`; } else { currentPage++; drawerLoading.innerText = '下滑加载更多...'; } isLoading = false; }) .catch(err => { isLoading = false; console.error('获取详情失败:', err); drawerLoading.innerText = '网络请求失败'; }); } // 点击事件监听 document.addEventListener('click', function(e) { // 仅在 #/love 页面介入 if (!window.location.hash.includes('love')) { return; } const cardItem = e.target.closest('.interaction-item'); if (!cardItem) return; // 按钮类元素原生放行 if ( e.target.closest('.interaction-item__avatar') || e.target.closest('.interaction-item__uname') || e.target.closest('.interaction-item__btn') || e.target.closest('.interaction-item__dnd') ) { return; } // 拦截并阻断清空当前列表 e.stopPropagation(); e.preventDefault(); if (!mask) initDrawerUI(); currentCardId = getCardIdFromElement(cardItem); currentPage = 1; isEnd = false; isLoading = false; loadedUserMids.clear(); drawerInfo.innerHTML = ''; drawerList.innerHTML = ''; drawerLoading.innerText = '数据加载中...'; mask.classList.add('active'); if (currentCardId) { loadNextPage(); } else { const originalFetch = window.fetch; let captured = false; window.fetch = function(...args) { const url = args[0] ? (typeof args[0] === 'string' ? args[0] : args[0].url) : ''; if (url && url.includes('like_detail') && url.includes('card_id=')) { const match = url.match(/card_id=(\d+)/); if (match && match[1] && !captured) { captured = true; currentCardId = match[1]; window.fetch = originalFetch; loadNextPage(); } } return originalFetch.apply(this, args); }; setTimeout(() => { window.fetch = originalFetch; if (!captured && !currentCardId) { drawerLoading.innerText = '未能匹配卡片 ID,请重试'; } }, 2000); } }, true); // 挂载抽屉容器 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initDrawerUI); } else { initDrawerUI(); } })();