批量管理哔哩哔哩订阅,可实现一键取消所有订阅。
// ==UserScript== // @name 哔哩哔哩订阅管理 / 批量取消订阅合集 // @author 安和(AHCorn) // @namespace https://github.com/AHCorn/Bilibili-Batch-Unsubscribe // @version 2.1 // @license GPL-3.0 // @description 批量管理哔哩哔哩订阅,可实现一键取消所有订阅。 // @grant GM_registerMenuCommand // @grant GM_addStyle // @match https://space.bilibili.com/*/favlist* // @run-at document-end // ==/UserScript== (function() { 'use strict'; GM_addStyle(` #bilibili-batch-unsubscribe-panel { position: fixed; top: 7%; left: 13%; right: 13%; bottom: 7%; z-index: 10000; background: linear-gradient(135deg, #f6f8fa, #e9ecef); border-radius: 24px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1), 0 1px 8px rgba(0, 0, 0, 0.06); padding: 40px; display: flex; flex-direction: column; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); transform: scale(1); opacity: 1; backdrop-filter: blur(10px); } #bilibili-batch-unsubscribe-panel .panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; font-size: 28px; color: #00a1d6; font-weight: 700; text-align: center; padding: 32px; background: #fff; border-radius: 24px; border-bottom: 2px solid rgba(0, 161, 214, 0.1); } #bilibili-batch-unsubscribe-panel .panel-header .title-container { display: flex; align-items: center; gap: 12px; } #bilibili-batch-unsubscribe-panel .panel-header .github-link { display: flex; align-items: center; cursor: pointer; transition: opacity 0.2s ease; } #bilibili-batch-unsubscribe-panel .panel-header .github-link:hover { opacity: 0.8; } #bilibili-batch-unsubscribe-panel .panel-header .github-icon { width: 28px; height: 28px; fill: #00a1d6; } #bilibili-batch-unsubscribe-panel .close-btn { cursor: pointer; font-size: 30px; color: #999; transition: all 0.2s ease; } #bilibili-batch-unsubscribe-panel .close-btn:hover { color: #666; transform: rotate(90deg); } #bilibili-batch-unsubscribe-panel .subscription-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; overflow-y: auto; padding: 20px; margin: 0; scrollbar-width: thin; scrollbar-color: rgba(0, 161, 214, 0.3) transparent; max-height: calc(100% - 160px); min-height: 200px; } #bilibili-batch-unsubscribe-panel .subscription-item { display: flex; align-items: center; padding: 16px; background-color: #fff; border: 1px solid #e0e0e0; border-radius: 12px; transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); cursor: pointer; user-select: none; position: relative; overflow: hidden; } #bilibili-batch-unsubscribe-panel .subscription-item:hover { background-color: #f8f8f8; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); border-color: rgba(0, 161, 214, 0.3); } #bilibili-batch-unsubscribe-panel .subscription-item.selected { background: rgba(0, 161, 214, 0.05); border-color: #00a1d6; } #bilibili-batch-unsubscribe-panel .subscription-item input[type='checkbox'] { display: none; } #bilibili-batch-unsubscribe-panel .subscription-item label { flex: 1; font-size: 14px; color: #18191c; margin: 0 12px; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 24px; } #bilibili-batch-unsubscribe-panel .subscription-item .view-link { padding: 6px 12px; background: rgba(0, 161, 214, 0.1); border-radius: 6px; color: #00a1d6; font-size: 13px; transition: all 0.2s ease; text-decoration: none; white-space: nowrap; opacity: 0; transform: translateX(10px); } #bilibili-batch-unsubscribe-panel .subscription-item:hover .view-link { opacity: 1; transform: translateX(0); } #bilibili-batch-unsubscribe-panel .subscription-item .view-link:hover { background: rgba(0, 161, 214, 0.2); } #bilibili-batch-unsubscribe-panel .action-buttons { display: flex; gap: 12px; padding: 24px; border-top: 2px solid rgba(0, 161, 214, 0.1); margin-top: auto; background: #fff; position: relative; z-index: 1; border-radius: 24px; } #bilibili-batch-unsubscribe-panel #search-input { flex: 1; padding: 12px 16px; border: 2px solid rgba(0, 161, 214, 0.2); border-radius: 12px; font-size: 14px; color: #18191c; transition: all 0.2s ease; background: #ffffff; } #bilibili-batch-unsubscribe-panel #search-input:focus { border-color: #00a1d6; box-shadow: 0 0 0 3px rgba(0, 161, 214, 0.1); outline: none; } @media screen and (min-width: 1440px) { #bilibili-batch-unsubscribe-panel .subscription-list { grid-template-columns: repeat(2, 1fr); gap: 20px; padding: 24px; } #bilibili-batch-unsubscribe-panel .subscription-item { padding: 20px; } #bilibili-batch-unsubscribe-panel .subscription-item label { font-size: 15px; } } @media screen and (max-width: 1200px) { #bilibili-batch-unsubscribe-panel .subscription-list { grid-template-columns: repeat(2, 1fr); gap: 16px; padding: 16px; } #bilibili-batch-unsubscribe-panel .subscription-item { padding: 14px; } } @media screen and (max-width: 768px) { #bilibili-batch-unsubscribe-panel { left: 5%; right: 5%; } #bilibili-batch-unsubscribe-panel .subscription-list { grid-template-columns: 1fr; gap: 12px; padding: 12px; } #bilibili-batch-unsubscribe-panel .action-buttons { flex-wrap: wrap; padding: 16px; } #bilibili-batch-unsubscribe-panel #search-input { width: 100%; margin-bottom: 12px; } #bilibili-batch-unsubscribe-panel .btn { flex: 1; min-width: 0; padding: 10px 16px; font-size: 13px; } } @media screen and (max-width: 480px) { #bilibili-batch-unsubscribe-panel { left: 0; right: 0; top: 0; bottom: 0; border-radius: 0; } #bilibili-batch-unsubscribe-panel .subscription-list { padding: 10px; gap: 10px; } #bilibili-batch-unsubscribe-panel .subscription-item { padding: 12px; } #bilibili-batch-unsubscribe-panel .action-buttons { padding: 12px; } #bilibili-batch-unsubscribe-panel .btn { padding: 8px 12px; font-size: 12px; border-radius: 8px; } } #bilibili-batch-unsubscribe-panel .subscription-list::-webkit-scrollbar { width: 6px; } #bilibili-batch-unsubscribe-panel .subscription-list::-webkit-scrollbar-track { background: transparent; } #bilibili-batch-unsubscribe-panel .subscription-list::-webkit-scrollbar-thumb { background-color: rgba(0, 161, 214, 0.3); border-radius: 3px; } #bilibili-batch-unsubscribe-panel .subscription-list::-webkit-scrollbar-thumb:hover { background-color: rgba(0, 161, 214, 0.5); } #bilibili-batch-unsubscribe-panel .btn { padding: 12px 24px; border: none; border-radius: 12px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; background: #00a1d6; color: #ffffff; white-space: nowrap; box-shadow: 0 2px 8px rgba(0, 161, 214, 0.2); } #bilibili-batch-unsubscribe-panel .btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 161, 214, 0.3); background: #00b5e5; } #bilibili-batch-unsubscribe-panel .btn:active { transform: translateY(0); box-shadow: 0 2px 4px rgba(0, 161, 214, 0.2); } #bilibili-batch-unsubscribe-panel #select-all, #bilibili-batch-unsubscribe-panel #deselect-all { background: rgba(0, 161, 214, 0.1); color: #00a1d6; box-shadow: none; } #bilibili-batch-unsubscribe-panel #select-all:hover, #bilibili-batch-unsubscribe-panel #deselect-all:hover { background: rgba(0, 161, 214, 0.2); box-shadow: 0 4px 12px rgba(0, 161, 214, 0.1); } #bilibili-batch-unsubscribe-panel #unsubscribe-selected { background: #fb7299; box-shadow: 0 2px 8px rgba(251, 114, 153, 0.2); } #bilibili-batch-unsubscribe-panel #unsubscribe-selected:hover { background: #fc8bab; box-shadow: 0 4px 12px rgba(251, 114, 153, 0.3); } #bilibili-batch-unsubscribe-panel .progress-container { margin-top: 20px; padding: 20px; background: rgba(255, 255, 255, 0.98); border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); border: 1px solid rgba(0, 161, 214, 0.1); display: none; backdrop-filter: blur(10px); } #bilibili-batch-unsubscribe-panel .progress-title { font-size: 16px; color: #00a1d6; margin-bottom: 15px; font-weight: 600; display: flex; align-items: center; } #bilibili-batch-unsubscribe-panel .progress-title::before { content: ''; display: inline-block; width: 6px; height: 6px; background: #00a1d6; border-radius: 50%; margin-right: 8px; animation: pulse 2s infinite; } #bilibili-batch-unsubscribe-panel .progress-bar { width: 100%; height: 6px; background: #f0f2f5; border-radius: 3px; overflow: hidden; margin: 10px 0; } #bilibili-batch-unsubscribe-panel .progress-bar-inner { height: 100%; background: linear-gradient(90deg, #6e8efb, #00a1d6); width: 0%; transition: width 0.3s ease; border-radius: 3px; box-shadow: 0 0 10px rgba(0, 161, 214, 0.3); } #bilibili-batch-unsubscribe-panel .progress-info { display: flex; justify-content: space-between; margin: 10px 0; font-size: 14px; color: #666; } #bilibili-batch-unsubscribe-panel .progress-count { font-family: 'Segoe UI', 'Roboto', sans-serif; } #bilibili-batch-unsubscribe-panel .progress-percentage { font-weight: 600; color: #00a1d6; } #bilibili-batch-unsubscribe-panel .button-container { display: flex; justify-content: center; margin-top: 15px; gap: 10px; } #bilibili-batch-unsubscribe-panel .abort-button, #bilibili-batch-unsubscribe-panel .return-button { padding: 8px 16px; border: none; border-radius: 12px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease; width: 100%; } #bilibili-batch-unsubscribe-panel .abort-button { background: #f25d8e; color: white; } #bilibili-batch-unsubscribe-panel .abort-button:hover { background: #e74d7b; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(242, 93, 142, 0.2); } #bilibili-batch-unsubscribe-panel .return-button { background: #00a1d6; color: white; display: none; } #bilibili-batch-unsubscribe-panel .return-button:hover { background: #0091c2; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 161, 214, 0.2); } #bilibili-batch-unsubscribe-panel .progress-container.completed .progress-title::before { animation: none; background: #00a1d6; } #bilibili-batch-unsubscribe-panel .progress-container.completed .progress-title { color: #00a1d6; } @keyframes pulse { 0% { transform: scale(0.95); opacity: 0.5; } 50% { transform: scale(1.05); opacity: 1; } 100% { transform: scale(0.95); opacity: 0.5; } } #unsubscribe-progress { position: fixed; bottom: 24px; right: 24px; width: 320px; background: rgba(255, 255, 255, 0.98); border-radius: 16px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); padding: 20px; z-index: 10001; backdrop-filter: blur(10px); border: 1px solid rgba(0, 161, 214, 0.1); animation: progressFadeIn 0.3s ease-out; display: none; } #bilibili-batch-unsubscribe-panel.hidden ~ #unsubscribe-progress { display: block; } @keyframes progressFadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } #unsubscribe-progress .progress-title { font-size: 16px; color: #00a1d6; font-weight: 600; margin-bottom: 16px; } #unsubscribe-progress .progress-bar { height: 6px; background: rgba(0, 161, 214, 0.1); border-radius: 3px; overflow: hidden; margin: 12px 0; position: relative; } #unsubscribe-progress .progress-bar-inner { height: 100%; background: linear-gradient(90deg, #00a1d6, #00b5e5); border-radius: 3px; width: 0%; transition: width 0.3s ease-out; } #unsubscribe-progress .progress-info { display: flex; justify-content: space-between; margin: 12px 0; font-size: 14px; color: #666; } #unsubscribe-progress .progress-percentage { color: #00a1d6; font-weight: 600; } #unsubscribe-progress .button-container { display: flex; gap: 8px; margin-top: 16px; } #unsubscribe-progress .abort-button, #unsubscribe-progress .return-button { flex: 1; padding: 10px; border: none; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } #unsubscribe-progress .abort-button { background: #fb7299; color: #ffffff; } #unsubscribe-progress .abort-button:hover { background: #fc8bab; transform: translateY(-1px); } #unsubscribe-progress .return-button { background: #00a1d6; color: #ffffff; } #unsubscribe-progress .return-button:hover { background: #00b5e5; transform: translateY(-1px); } .hidden { display: none !important; opacity: 0; visibility: hidden; } #unsubscribe-progress, #bilibili-batch-unsubscribe-panel .progress-container { display: none; } #bilibili-batch-unsubscribe-panel .loading-message { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(255, 255, 255, 0.95); padding: 20px 40px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); font-size: 16px; color: #00a1d6; display: flex; align-items: center; gap: 10px; z-index: 1000; } #bilibili-batch-unsubscribe-panel .loading-message::before { content: ''; display: inline-block; width: 20px; height: 20px; border: 3px solid #00a1d6; border-top-color: transparent; border-radius: 50%; animation: loading-spin 1s linear infinite; } @keyframes loading-spin { to { transform: rotate(360deg); } } @media (max-width: 1200px) { #bilibili-batch-unsubscribe-panel .subscription-list { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 768px) { #bilibili-batch-unsubscribe-panel .subscription-list { grid-template-columns: 1fr; } } `); const panelHTML = ` <div class="panel-header"> <div class="title-container"> <div>批量管理订阅</div> <a href="https://github.com/AHCorn/Bilibili-Batch-Unsubscribe" target="_blank" class="github-link" title="⭐"> <svg class="github-icon" viewBox="0 0 16 16" version="1.1" aria-hidden="true"> <path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path> </svg> </a> </div> <div class="close-btn" title="关闭">✖</div> </div> <div class="subscription-list"></div> <div class="action-buttons"> <input type="text" id="search-input" placeholder="关键字搜索"> <button class="btn" id="select-all">全选</button> <button class="btn" id="deselect-all">取消全选</button> <button class="btn" id="unsubscribe-selected">取消订阅</button> </div> <div class="progress-container"> <div class="progress-title">正在取消订阅</div> <div class="progress-bar"> <div class="progress-bar-inner"></div> </div> <div class="progress-info"> <span class="progress-count">0/0</span> <span class="progress-percentage">0%</span> </div> <div class="button-container"> <button class="abort-button">中止操作</button> <button class="return-button">返回管理面板</button> </div> </div> `; const panel = document.createElement('div'); panel.id = 'bilibili-batch-unsubscribe-panel'; panel.className = 'hidden'; panel.innerHTML = panelHTML; document.body.appendChild(panel); const subscriptionList = panel.querySelector('.subscription-list'); const loadingMessage = panel.querySelector('.loading-message'); async function simulateMouseEvents(element, events = ['mouseover', 'mouseenter', 'mousemove']) { const rect = element.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; for (let eventType of events) { try { element.dispatchEvent(new Event(eventType, { bubbles: true, cancelable: true })); if (eventType === 'mouseover') { element.dispatchEvent(new Event('mouseenter', { bubbles: false, cancelable: true })); } } catch (error) { console.error(`创建${eventType}事件失败:`, error); } await new Promise(resolve => setTimeout(resolve, 100)); } } async function confirmAndUnsubscribe(title) { const isNewUI = document.querySelector('.vui_collapse_item') !== null; if (!isNewUI) { const subscriptionItems = Array.from(panel.querySelectorAll('.subscription-item')); for (let item of subscriptionItems) { const itemTitle = item.querySelector('label').textContent.trim(); if (itemTitle === title) { const fid = item.querySelector('input[type="checkbox"]').value; await simulateUnsubscribeByTitle(title, fid); break; } } return; } const subscriptionItems = Array.from(panel.querySelectorAll('.subscription-item')); for (let item of subscriptionItems) { const itemTitle = item.querySelector('label').textContent.trim(); if (itemTitle === title) { const originalItem = document.querySelector(`div[title="${title}"]`); if (originalItem) { const titleArea = originalItem.querySelector('.vui_ellipsis.multi-mode'); if (titleArea) { try { await new Promise(async (resolve, reject) => { let moreIcon = null; let iconVisible = false; let dialogObserver = null; let unsubscribeClicked = false; let confirmClicked = false; let retryCount = 0; const MAX_RETRIES = 3; const TIMEOUT_DURATION = 5000; let timeoutId; dialogObserver = new MutationObserver((mutations) => { if (unsubscribeClicked && !confirmClicked) { const dialogs = document.querySelectorAll('.vui_dialog--content'); for (const dialog of dialogs) { const title = dialog.querySelector('.vui_dialog--title'); const body = dialog.querySelector('.vui_dialog--body'); if (title?.textContent === '确认提示' && body?.textContent === '确定取消订阅吗?') { const confirmBtn = dialog.querySelector('.vui_button--blue.vui_dialog--btn-confirm'); if (confirmBtn) { confirmClicked = true; clearTimeout(timeoutId); setTimeout(() => { try { confirmBtn.click(); setTimeout(() => { const modalRoot = dialog.closest('.vui_dialog--root'); if (modalRoot) { const modalContainer = modalRoot.parentElement; if (modalContainer) { modalContainer.remove(); } } dialogObserver.disconnect(); resolve(); }, 300); } catch (error) { console.error('点击确认按钮失败:', error); reject(error); } }, 200); } } } } }); dialogObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); const observer = new MutationObserver(async (mutations) => { if (!iconVisible) { moreIcon = originalItem.querySelector('.sic-BDC-more_vertical_fill'); if (moreIcon && window.getComputedStyle(moreIcon).display !== 'none') { iconVisible = true; await simulateMouseEvents(moreIcon, ['mouseenter', 'mouseover']); } } if (iconVisible && !unsubscribeClicked) { const menuPanel = document.querySelector('.menu-popover__panel'); if (menuPanel) { const unsubscribeButton = Array.from(menuPanel.querySelectorAll('.menu-popover__panel-item')) .find(btn => btn.textContent.trim() === '取消订阅'); if (unsubscribeButton) { unsubscribeClicked = true; observer.disconnect(); unsubscribeButton.click(); } } } }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'display'] }); const trySimulateEvents = async () => { try { await simulateMouseEvents(titleArea); } catch (error) { if (retryCount < MAX_RETRIES) { retryCount++; await new Promise(resolve => setTimeout(resolve, 500)); await trySimulateEvents(); } else { reject('模拟鼠标事件失败,已达到最大重试次数'); } } }; await trySimulateEvents(); timeoutId = setTimeout(() => { if (!confirmClicked) { observer.disconnect(); dialogObserver.disconnect(); if (retryCount < MAX_RETRIES) { retryCount++; console.log(`重试第 ${retryCount} 次...`); trySimulateEvents(); } else { reject('操作超时,已达到最大重试次数'); } } }, TIMEOUT_DURATION); }); await new Promise(resolve => setTimeout(resolve, 500)); } catch (error) { console.error(`取消订阅失败: ${title}`, error); throw error; } } } break; } } } async function simulateUnsubscribeByTitle(title, fid) { return new Promise((resolve, reject) => { console.log(`正在尝试取消订阅: ${title}`); const favItem = document.querySelector(`li[fid="${fid}"] a[title="${title}"]`); if (favItem) { const unsubscribeButton = favItem.closest('li').querySelector('.be-dropdown-item'); if (unsubscribeButton) { unsubscribeButton.click(); setTimeout(() => { console.log(`取消订阅: ${title} 成功`); resolve(); }, 1000); } else { console.error('未找到取消订阅按钮'); reject('未找到取消订阅按钮'); } } else { console.error('未找到对应的订阅项'); reject('未找到对应的订阅项'); } }); } function togglePanel() { if (panel.classList.contains('hidden')) { panel.classList.remove('hidden'); loadAndDisplaySubscriptions(); } else { panel.classList.add('hidden'); } } async function loadAndDisplaySubscriptions() { const loadingMessage = document.createElement('div'); loadingMessage.className = 'loading-message'; loadingMessage.textContent = '正在加载订阅列表,请稍候...'; panel.appendChild(loadingMessage); const collapseHeaders = Array.from(document.querySelectorAll('.vui_collapse_item_header')); const targetHeader = collapseHeaders.find(header => header.textContent.includes('我追的合集/收藏夹')); let favListContainer; if (targetHeader) { const collapseItem = targetHeader.closest('.vui_collapse_item'); const scrollContainer = collapseItem.querySelector('.vui_collapse_item_content'); if (scrollContainer) { let lastHeight = scrollContainer.scrollHeight; let attempts = 0; const MAX_ATTEMPTS = 50; await new Promise((resolve) => { const tryScroll = () => { const currentHeight = scrollContainer.scrollHeight; scrollContainer.scrollTop = currentHeight; if (lastHeight !== currentHeight) { lastHeight = currentHeight; attempts = 0; setTimeout(tryScroll, 100); } else if (attempts < MAX_ATTEMPTS) { attempts++; setTimeout(tryScroll, 100); } else { resolve(); } }; tryScroll(); }); } const sidebarItems = collapseItem.querySelectorAll('.fav-sidebar-item'); favListContainer = document.createElement('div'); favListContainer.className = 'fav-list-container'; sidebarItems.forEach(item => { const title = item.getAttribute('title'); const link = item.querySelector('.vui_sidebar-item-title')?.parentElement?.closest('a')?.href || '#'; const count = item.querySelector('.vui_sidebar-item-right')?.textContent.trim() || '0'; const favItem = document.createElement('div'); favItem.className = 'fav-item'; favItem.setAttribute('fid', title); favItem.innerHTML = ` <a class="text" title="${title}" href="${link}">${title}</a> <span class="be-dropdown-item">取消订阅</span> `; favListContainer.appendChild(favItem); }); } else { const containers = Array.from(document.querySelectorAll('.nav-container.fav-container')); const targetContainer = containers.find(container => container.querySelector('p')?.textContent.includes('我的收藏和订阅')); if (targetContainer) { favListContainer = targetContainer.querySelector('.fav-list-container'); if (favListContainer) { let lastHeight = favListContainer.scrollHeight; let attempts = 0; await new Promise((resolve) => { const checkScroll = () => { const currentHeight = favListContainer.scrollHeight; favListContainer.scrollTop += favListContainer.clientHeight / 2; if (lastHeight !== currentHeight) { lastHeight = currentHeight; attempts = 0; setTimeout(checkScroll, 100); } else if (attempts < 50) { attempts++; setTimeout(checkScroll, 100); } else { console.log('没有更多内容'); displayLoadedSubscriptions(targetContainer); panel.removeChild(loadingMessage); resolve(); } }; checkScroll(); }); return; } } } if (!favListContainer) { console.error('未找到订阅列表'); panel.removeChild(loadingMessage); return; } displayLoadedSubscriptions(favListContainer); panel.removeChild(loadingMessage); } function displayLoadedSubscriptions(container) { const items = container.querySelectorAll('.fav-item'); const subscriptionList = document.querySelector('.subscription-list'); subscriptionList.innerHTML = ''; const isNewUI = document.querySelector('.vui_collapse_item') !== null; items.forEach((item) => { const title = item.querySelector('.text').title; const link = item.querySelector('.text').href; const fid = item.getAttribute('fid'); const listItem = document.createElement('div'); listItem.classList.add('subscription-item'); listItem.innerHTML = `<input type="checkbox" value="${fid}" class="subscription-checkbox"> <label>${title}</label> <a href="javascript:void(0)" class="view-link">查看</a>`; listItem.addEventListener('click', (e) => { if (e.target.classList.contains('view-link')) { if (isNewUI) { const originalItem = document.querySelector(`div[title="${title}"]`); if (originalItem) { panel.style.display = 'none'; panel.classList.add('hidden'); const progressElement = document.getElementById('unsubscribe-progress'); if (progressElement) { progressElement.style.display = 'block'; progressElement.querySelector('.progress-title').textContent = '正在查看订阅'; progressElement.querySelector('.progress-bar').style.display = 'none'; progressElement.querySelector('.progress-info').style.display = 'none'; progressElement.querySelector('.abort-button').style.display = 'none'; const returnButton = progressElement.querySelector('.return-button'); returnButton.style.display = 'block'; returnButton.textContent = '返回订阅管理'; } const titleLink = originalItem.querySelector('.vui_sidebar-item-title'); if (titleLink) { titleLink.click(); } } } else { window.open(link, '_blank'); } return; } e.preventDefault(); e.stopPropagation(); const checkbox = listItem.querySelector('input[type="checkbox"]'); checkbox.checked = !checkbox.checked; listItem.classList.toggle('selected', checkbox.checked); }); subscriptionList.appendChild(listItem); }); } const closeBtn = panel.querySelector('.close-btn'); closeBtn.addEventListener('click', togglePanel); const selectAllBtn = panel.querySelector('#select-all'); selectAllBtn.addEventListener('click', () => { const visibleItems = panel.querySelectorAll('.subscription-item:not([style*="display: none"])'); visibleItems.forEach((item) => { const checkbox = item.querySelector('input[type="checkbox"]'); checkbox.checked = true; item.classList.add('selected'); }); }); const deselectAllBtn = panel.querySelector('#deselect-all'); deselectAllBtn.addEventListener('click', () => { const items = panel.querySelectorAll('.subscription-item'); items.forEach((item) => { const checkbox = item.querySelector('input[type="checkbox"]'); checkbox.checked = false; item.classList.remove('selected'); }); }); let isAborting = false; const progressHTML = ` <div id="unsubscribe-progress"> <div class="progress-title">正在取消订阅</div> <div class="progress-bar"> <div class="progress-bar-inner"></div> </div> <div class="progress-info"> <span class="progress-count">0/0</span> <span class="progress-percentage">0%</span> </div> <div class="button-container"> <button class="abort-button">中止操作</button> <button class="return-button">返回管理面板</button> </div> </div> `; function initializeProgress() { const oldProgress = document.getElementById('unsubscribe-progress'); if (oldProgress) { oldProgress.remove(); } document.body.insertAdjacentHTML('beforeend', progressHTML); const progressElement = document.getElementById('unsubscribe-progress'); const returnButton = progressElement.querySelector('.return-button'); progressElement.style.display = 'none'; returnButton.addEventListener('click', () => { progressElement.style.display = 'none'; panel.style.display = 'flex'; panel.classList.remove('hidden'); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeProgress); } else { initializeProgress(); } function updateProgress(current, total) { requestAnimationFrame(() => { const progressElement = document.getElementById('unsubscribe-progress'); if (!progressElement) { console.error('进度窗口未找到,重新初始化...'); initializeProgress(); return; } const progressBar = progressElement.querySelector('.progress-bar-inner'); const progressCount = progressElement.querySelector('.progress-count'); const progressPercentage = progressElement.querySelector('.progress-percentage'); const percentage = Math.round((current / total) * 100); progressBar.style.width = `${percentage}%`; progressCount.textContent = `${current}/${total}`; progressPercentage.textContent = `${percentage}%`; }); } const unsubscribeSelectedBtn = panel.querySelector('#unsubscribe-selected'); unsubscribeSelectedBtn.addEventListener('click', async () => { const checkedItems = panel.querySelectorAll('.subscription-item input[type="checkbox"]:checked'); if (checkedItems.length === 0) return; const isNewUI = document.querySelector('.vui_collapse_item') !== null; if (isNewUI) { panel.style.display = 'none'; panel.classList.add('hidden'); let progressElement = document.getElementById('unsubscribe-progress'); if (!progressElement) { initializeProgress(); progressElement = document.getElementById('unsubscribe-progress'); } progressElement.style.display = 'block'; isAborting = false; const abortButton = progressElement.querySelector('.abort-button'); const returnButton = progressElement.querySelector('.return-button'); const titleElement = progressElement.querySelector('.progress-title'); const progressBarContainer = progressElement.querySelector('.progress-bar'); const progressInfo = progressElement.querySelector('.progress-info'); progressElement.classList.remove('completed'); progressBarContainer.style.display = 'block'; progressInfo.style.display = 'block'; abortButton.style.display = 'block'; returnButton.style.display = 'none'; titleElement.textContent = '正在取消订阅'; const progressBar = progressElement.querySelector('.progress-bar-inner'); progressBar.style.width = '0%'; abortButton.removeEventListener('click', abortButton.abortHandler); returnButton.removeEventListener('click', returnButton.returnHandler); const abortHandler = () => { isAborting = true; progressElement.style.display = 'none'; panel.style.display = 'flex'; panel.classList.remove('hidden'); }; abortButton.abortHandler = abortHandler; abortButton.addEventListener('click', abortHandler); const returnHandler = () => { progressElement.style.display = 'none'; panel.style.display = 'flex'; panel.classList.remove('hidden'); }; returnButton.returnHandler = returnHandler; returnButton.addEventListener('click', returnHandler); let current = 0; const total = checkedItems.length; for (let checkbox of checkedItems) { if (isAborting) break; const title = checkbox.nextElementSibling.textContent.trim(); await confirmAndUnsubscribe(title); current++; updateProgress(current, total); } if (!isAborting) { progressElement.classList.add('completed'); titleElement.textContent = '取消订阅完成'; abortButton.style.display = 'none'; returnButton.style.display = 'block'; } } else { for (let checkbox of checkedItems) { const title = checkbox.nextElementSibling.textContent.trim(); const fid = checkbox.value; await simulateUnsubscribeByTitle(title, fid); } } loadAndDisplaySubscriptions(); }); const searchInput = panel.querySelector('#search-input'); searchInput.addEventListener('input', () => { const searchTerm = searchInput.value.toLowerCase().trim(); const subscriptionItems = panel.querySelectorAll('.subscription-item'); subscriptionItems.forEach((item) => { const title = item.querySelector('label').textContent.trim().toLowerCase(); if (title.includes(searchTerm)) { item.style.display = 'flex'; } else { item.style.display = 'none'; } }); }); GM_registerMenuCommand('订阅管理', togglePanel); })();