哔哩哔哩订阅管理 / 批量取消订阅合集

批量管理哔哩哔哩订阅,可实现一键取消所有订阅。

// ==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);
})();