B站动态日期跳转助手

快速跳转到B站UP主指定年月日的动态,支持自定义滚动参数和重试次数,适合查找历史动态

// ==UserScript==
// @name         B站动态日期跳转助手
// @name:en      Bilibili Dynamic Time Jumper
// @namespace    https://github.com/tongle2025/bilibili-dynamic-jumper
// @version      1.0.3
// @description  快速跳转到B站UP主指定年月日的动态,支持自定义滚动参数和重试次数,适合查找历史动态
// @description:en Quickly jump to Bilibili UP master's dynamics at specified date
// @author       Sakurakid
// @match        https://space.bilibili.com/*/dynamic
// @match        https://t.bilibili.com/*
// @icon         https://www.bilibili.com/favicon.ico
// @grant        none
// @license      MIT
// @homepage     https://github.com/tongle2025/bilibili-dynamic-jumper
// @supportURL   https://github.com/tongle2025/bilibili-dynamic-jumper/issues
// ==/UserScript==

(function() {
    'use strict';

    // 全局状态
    let isSearching = false;
    let targetYear = null;
    let targetMonth = null;
    let targetDay = null; // 新增:目标日期(可选)
    let currentRetries = 0;
    let lastDynamicCount = 0;
    let totalScrolls = 0;

    // 设置存储的键名
    const STORAGE_KEY = 'bilibili_dynamic_jumper_settings';

    function debugLog(...args) {
        console.log('[动态跳转]', ...args);
    }

    // 保存设置到localStorage
    function saveSettings() {
        try {
            const settings = {
                maxRetries: document.getElementById('max-retries').value,
                scrollDelay: document.getElementById('scroll-delay').value,
                scrollAggressiveness: document.getElementById('scroll-aggressiveness').value,
                extraScroll: document.getElementById('extra-scroll').value
            };
            localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
            debugLog('设置已保存', settings);
        } catch (e) {
            debugLog('保存设置失败:', e);
        }
    }

    // 从localStorage加载设置
    function loadSettings() {
        try {
            const saved = localStorage.getItem(STORAGE_KEY);
            if (saved) {
                const settings = JSON.parse(saved);
                debugLog('加载已保存的设置', settings);
                return settings;
            }
        } catch (e) {
            debugLog('加载设置失败:', e);
        }
        return null;
    }

    function createControlPanel() {
        // 先创建最小化的圆形按钮
        createMinimizedButton();
    }

    // 创建最小化按钮(默认状态)
    function createMinimizedButton() {
        const showBtn = document.createElement('button');
        showBtn.id = 'show-panel-btn';
        showBtn.innerHTML = '📅';
        showBtn.title = '显示动态跳转面板';
        showBtn.style.cssText = `
            position: fixed;
            top: 80px;
            right: 20px;
            z-index: 10000;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            font-size: 24px;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5);
            transition: transform 0.2s, box-shadow 0.2s;
        `;

        showBtn.addEventListener('click', () => {
            showBtn.remove();
            createExpandedPanel();
        });

        showBtn.addEventListener('mouseenter', () => {
            showBtn.style.transform = 'scale(1.1)';
            showBtn.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.6)';
        });

        showBtn.addEventListener('mouseleave', () => {
            showBtn.style.transform = 'scale(1)';
            showBtn.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.5)';
        });

        document.body.appendChild(showBtn);
        debugLog('最小化按钮已创建');
    }

    // 创建展开的完整面板
    function createExpandedPanel() {
        const panel = document.createElement('div');
        panel.id = 'dynamic-jumper-panel';
        panel.innerHTML = `
            <div style="position: fixed; top: 80px; right: 20px; z-index: 10000;
                        background: white; padding: 20px; border-radius: 12px;
                        box-shadow: 0 4px 20px rgba(0,0,0,0.2); min-width: 340px; max-width: 400px;
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">
                <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;
                            border-bottom: 2px solid #00a1d6; padding-bottom: 10px;">
                    <h3 style="margin: 0; font-size: 17px; color: #333;">
                        📅 动态时间跳转 v1.0.3
                    </h3>
                    <button id="hide-panel" style="background: transparent; border: none; cursor: pointer;
                                                    font-size: 18px; color: #999; padding: 4px 8px;
                                                    transition: color 0.2s;" title="最小化面板">
                        ✕
                    </button>
                </div>

                <!-- 基本设置 -->
                <div style="margin-bottom: 12px;">
                    <label style="display: block; margin-bottom: 5px; font-size: 14px; color: #666; font-weight: 500;">
                        目标年份:
                    </label>
                    <select id="target-year"
                            style="width: 100%; padding: 8px; border: 1px solid #ddd;
                                   border-radius: 6px; font-size: 14px;">
                    </select>
                </div>
                <div style="margin-bottom: 12px;">
                    <label style="display: block; margin-bottom: 5px; font-size: 14px; color: #666; font-weight: 500;">
                        目标月份:
                    </label>
                    <select id="target-month"
                            style="width: 100%; padding: 8px; border: 1px solid #ddd;
                                   border-radius: 6px; font-size: 14px;">
                        <option value="1">1月</option>
                        <option value="2">2月</option>
                        <option value="3">3月</option>
                        <option value="4">4月</option>
                        <option value="5">5月</option>
                        <option value="6">6月</option>
                        <option value="7">7月</option>
                        <option value="8">8月</option>
                        <option value="9">9月</option>
                        <option value="10">10月</option>
                        <option value="11">11月</option>
                        <option value="12">12月</option>
                    </select>
                </div>
                <div style="margin-bottom: 15px;">
                    <label style="display: block; margin-bottom: 5px; font-size: 14px; color: #666; font-weight: 500;">
                        目标日期 (可选):
                    </label>
                    <select id="target-day"
                            style="width: 100%; padding: 8px; border: 1px solid #ddd;
                                   border-radius: 6px; font-size: 14px;">
                        <option value="">不指定具体日期</option>
                    </select>
                    <div style="font-size: 11px; color: #999; margin-top: 4px;">
                        留空则匹配整个月的动态,指定则精确到具体某天
                    </div>
                </div>

                <!-- 高级设置(可折叠) -->
                <div style="margin-bottom: 15px; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden;">
                    <div id="advanced-toggle" style="padding: 10px 12px; background: #f8f9fa; cursor: pointer;
                                                     display: flex; justify-content: space-between; align-items: center;">
                        <span style="font-size: 14px; font-weight: 500; color: #555;">⚙️ 高级设置</span>
                        <span id="toggle-icon" style="font-size: 12px; color: #999;">▼</span>
                    </div>
                    <div id="advanced-panel" style="padding: 15px; background: #fafbfc; display: none;">

                        <!-- 最大重试次数 -->
                        <div style="margin-bottom: 15px;">
                            <label style="display: block; margin-bottom: 5px; font-size: 13px; color: #555; font-weight: 500;">
                                最大重试次数:
                            </label>
                            <input type="number" id="max-retries" value="10" min="1" max="50"
                                   style="width: 100%; padding: 6px 8px; border: 1px solid #ddd;
                                          border-radius: 4px; font-size: 13px;">
                            <div style="font-size: 11px; color: #888; margin-top: 4px; line-height: 1.4;">
                                当连续N次滚动都没有加载出新动态时停止。数值越大等待时间越长,适合搜索很早期的动态。
                            </div>
                        </div>

                        <!-- 滚动等待时间 -->
                        <div style="margin-bottom: 15px;">
                            <label style="display: block; margin-bottom: 5px; font-size: 13px; color: #555; font-weight: 500;">
                                滚动等待时间(毫秒):
                            </label>
                            <input type="number" id="scroll-delay" value="2000" min="500" max="10000" step="500"
                                   style="width: 100%; padding: 6px 8px; border: 1px solid #ddd;
                                          border-radius: 4px; font-size: 13px;">
                            <div style="font-size: 11px; color: #888; margin-top: 4px; line-height: 1.4;">
                                每次滚动后等待多久才检查新动态。数值越大越稳定,但速度越慢。推荐1500-3000毫秒。
                            </div>
                        </div>

                        <!-- 滚动激进度 -->
                        <div style="margin-bottom: 15px;">
                            <label style="display: block; margin-bottom: 5px; font-size: 13px; color: #555; font-weight: 500;">
                                滚动激进度:
                            </label>
                            <select id="scroll-aggressiveness"
                                    style="width: 100%; padding: 6px 8px; border: 1px solid #ddd;
                                           border-radius: 4px; font-size: 13px;">
                                <option value="normal">普通 - 滚动到页面底部</option>
                                <option value="aggressive" selected>激进 - 底部+额外滚动</option>
                                <option value="extreme">极限 - 多次超量滚动</option>
                            </select>
                            <div style="font-size: 11px; color: #888; margin-top: 4px; line-height: 1.4;">
                                控制滚动的力度。如果遇到加载困难,可以尝试"极限"模式。
                            </div>
                        </div>

                        <!-- 额外滚动距离 -->
                        <div style="margin-bottom: 0;">
                            <label style="display: block; margin-bottom: 5px; font-size: 13px; color: #555; font-weight: 500;">
                                额外滚动距离(像素):
                            </label>
                            <input type="number" id="extra-scroll" value="2000" min="0" max="10000" step="500"
                                   style="width: 100%; padding: 6px 8px; border: 1px solid #ddd;
                                          border-radius: 4px; font-size: 13px;">
                            <div style="font-size: 11px; color: #888; margin-top: 4px; line-height: 1.4;">
                                在滚动到底部后,再额外向下滚动的距离。这能更可靠地触发懒加载机制。
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 操作按钮 -->
                <button id="start-jump"
                        style="width: 100%; padding: 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                               color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 15px;
                               font-weight: 600; box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
                               transition: transform 0.2s, box-shadow 0.2s;">
                    🚀 开始搜索
                </button>
                <button id="stop-jump"
                        style="width: 100%; padding: 12px; background: #ff6b6b; color: white;
                               border: none; border-radius: 8px; cursor: pointer; font-size: 15px;
                               font-weight: 600; margin-top: 10px; display: none;
                               box-shadow: 0 4px 12px rgba(255, 107, 107, 0.4);">
                    ⏸️ 停止搜索
                </button>

                <!-- 进度信息 -->
                <div id="progress-info"
                     style="margin-top: 15px; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                            border-radius: 8px; font-size: 13px; color: white; display: none;
                            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);">
                    <div style="margin-bottom: 12px; font-weight: 500;">
                        📍 当前位置:
                        <div id="current-date" style="margin-top: 5px; font-size: 16px; font-weight: bold;
                                                      text-shadow: 0 2px 4px rgba(0,0,0,0.2);">
                            等待开始...
                        </div>
                    </div>
                    <div style="font-size: 12px; opacity: 0.95; border-top: 1px solid rgba(255,255,255,0.3);
                                padding-top: 10px; margin-top: 10px;">
                        已加载 <span id="loaded-count" style="font-weight: bold;">0</span> 条动态 |
                        滚动 <span id="scroll-count" style="font-weight: bold;">0</span> 次 |
                        重试 <span id="retry-count" style="font-weight: bold;">0</span>/<span id="max-retry-display">10</span>
                    </div>
                    <div id="status-msg" style="margin-top: 10px; font-size: 12px; opacity: 0.9;
                                                background: rgba(255,255,255,0.15); padding: 8px; border-radius: 4px;"></div>
                </div>

                <!-- 提示信息 -->
                <div style="margin-top: 12px; padding: 12px; background: #e7f3ff; border-left: 3px solid #00a1d6;
                            border-radius: 4px; font-size: 12px; color: #555; line-height: 1.6;">
                    <strong>💡 使用技巧:</strong><br>
                    • 搜索早期动态(如2019年)需要较长时间,请耐心等待<br>
                    • 如果长时间无进展,尝试增加"最大重试次数"或使用"极限"滚动模式<br>
                    • 滚动过快或过多可能会触发b站机制,需要手动过一下验证,无其他影响<br>
                    • 观察"当前位置"来判断搜索进度
                </div>
            </div>
        `;
        document.body.appendChild(panel);

        // 填充年份选项
        const yearSelect = document.getElementById('target-year');
        const currentYear = new Date().getFullYear();
        for (let year = currentYear; year >= 2009; year--) {
            const option = document.createElement('option');
            option.value = year;
            option.textContent = year + '年';
            yearSelect.appendChild(option);
        }

        // 填充日期选项(1-31日)
        const daySelect = document.getElementById('target-day');
        for (let day = 1; day <= 31; day++) {
            const option = document.createElement('option');
            option.value = day;
            option.textContent = day + '日';
            daySelect.appendChild(option);
        }

        // 监听年月变化,动态调整可选日期范围
        function updateDayOptions() {
            const year = parseInt(yearSelect.value);
            const month = parseInt(document.getElementById('target-month').value);
            const daysInMonth = new Date(year, month, 0).getDate();

            // 移除超出当月天数的选项
            const options = daySelect.querySelectorAll('option');
            options.forEach(option => {
                if (option.value && parseInt(option.value) > daysInMonth) {
                    option.style.display = 'none';
                } else {
                    option.style.display = 'block';
                }
            });

            // 如果当前选中的日期超出范围,重置为空
            if (daySelect.value && parseInt(daySelect.value) > daysInMonth) {
                daySelect.value = '';
            }
        }

        yearSelect.addEventListener('change', updateDayOptions);
        document.getElementById('target-month').addEventListener('change', updateDayOptions);

        // 加载保存的设置
        const savedSettings = loadSettings();
        if (savedSettings) {
            document.getElementById('max-retries').value = savedSettings.maxRetries || 10;
            document.getElementById('scroll-delay').value = savedSettings.scrollDelay || 2000;
            document.getElementById('scroll-aggressiveness').value = savedSettings.scrollAggressiveness || 'aggressive';
            document.getElementById('extra-scroll').value = savedSettings.extraScroll || 2000;
        }

        // 绑定设置变化事件,自动保存
        document.getElementById('max-retries').addEventListener('change', saveSettings);
        document.getElementById('scroll-delay').addEventListener('change', saveSettings);
        document.getElementById('scroll-aggressiveness').addEventListener('change', saveSettings);
        document.getElementById('extra-scroll').addEventListener('change', saveSettings);

        // 绑定事件
        document.getElementById('start-jump').addEventListener('click', startJump);
        document.getElementById('stop-jump').addEventListener('click', stopJump);

        // 隐藏/显示面板功能
        document.getElementById('hide-panel').addEventListener('click', () => {
            const panelDiv = document.getElementById('dynamic-jumper-panel').firstElementChild;
            panelDiv.style.display = 'none';
            createMinimizedButton();
        });

        // 隐藏按钮悬停效果
        const hideBtn = document.getElementById('hide-panel');
        hideBtn.addEventListener('mouseenter', () => {
            hideBtn.style.color = '#ff6b6b';
        });
        hideBtn.addEventListener('mouseleave', () => {
            hideBtn.style.color = '#999';
        });

        // 高级设置折叠功能
        document.getElementById('advanced-toggle').addEventListener('click', () => {
            const panel = document.getElementById('advanced-panel');
            const icon = document.getElementById('toggle-icon');
            if (panel.style.display === 'none') {
                panel.style.display = 'block';
                icon.textContent = '▲';
            } else {
                panel.style.display = 'none';
                icon.textContent = '▼';
            }
        });

        // 按钮悬停效果
        const startBtn = document.getElementById('start-jump');
        startBtn.addEventListener('mouseenter', () => {
            startBtn.style.transform = 'translateY(-2px)';
            startBtn.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.5)';
        });
        startBtn.addEventListener('mouseleave', () => {
            startBtn.style.transform = 'translateY(0)';
            startBtn.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.4)';
        });

        debugLog('控制面板已创建');
    }

    function startJump() {
        targetYear = parseInt(document.getElementById('target-year').value);
        targetMonth = parseInt(document.getElementById('target-month').value);
        const dayValue = document.getElementById('target-day').value;
        targetDay = dayValue ? parseInt(dayValue) : null; // 如果没选择日期则为null

        const targetDesc = targetDay
            ? `${targetYear}年${targetMonth}月${targetDay}日`
            : `${targetYear}年${targetMonth}月`;
        debugLog(`========== 开始搜索: ${targetDesc} ==========`);

        // 读取用户设置的参数
        const maxRetries = parseInt(document.getElementById('max-retries').value);
        const scrollDelay = parseInt(document.getElementById('scroll-delay').value);
        const aggressiveness = document.getElementById('scroll-aggressiveness').value;
        const extraScroll = parseInt(document.getElementById('extra-scroll').value);

        debugLog(`参数设置: 最大重试=${maxRetries}, 滚动延迟=${scrollDelay}ms, 激进度=${aggressiveness}, 额外滚动=${extraScroll}px`);

        // 保存到全局配置
        window.jumpConfig = {
            maxRetries: maxRetries,
            scrollDelay: scrollDelay,
            aggressiveness: aggressiveness,
            extraScroll: extraScroll
        };

        isSearching = true;
        currentRetries = 0;
        lastDynamicCount = 0;
        totalScrolls = 0;

        document.getElementById('start-jump').style.display = 'none';
        document.getElementById('stop-jump').style.display = 'block';
        document.getElementById('progress-info').style.display = 'block';
        document.getElementById('current-date').textContent = '初始化中...';
        document.getElementById('max-retry-display').textContent = maxRetries;
        document.getElementById('retry-count').textContent = '0';
        updateStatus('准备开始搜索...');

        // 先做一次小幅滚动,触发初始加载
        window.scrollBy(0, 500);
        setTimeout(autoScroll, 1000);
    }

    function stopJump() {
        isSearching = false;
        document.getElementById('start-jump').style.display = 'block';
        document.getElementById('stop-jump').style.display = 'none';
        document.getElementById('current-date').textContent = '已手动停止';
        updateStatus('');
        debugLog('========== 搜索已停止 ==========');
    }

    function updateStatus(msg) {
        document.getElementById('status-msg').textContent = msg;
    }

    // 超级激进的滚动策略
    function autoScroll() {
        if (!isSearching) return;

        totalScrolls++;
        document.getElementById('scroll-count').textContent = totalScrolls;

        const config = window.jumpConfig;
        const scrollHeight = document.documentElement.scrollHeight;
        const clientHeight = document.documentElement.clientHeight;

        debugLog(`第${totalScrolls}次滚动 - 页面高度:${scrollHeight}, 可见高度:${clientHeight}`);

        updateStatus('🔄 执行激进滚动策略...');

        // 根据激进度选择不同的滚动策略
        if (config.aggressiveness === 'normal') {
            // 普通模式:只滚动到底部
            window.scrollTo({
                top: scrollHeight,
                behavior: 'auto'
            });
            debugLog('使用普通滚动:scrollTo(scrollHeight)');

        } else if (config.aggressiveness === 'aggressive') {
            // 激进模式:滚动到底部 + 额外滚动
            window.scrollTo({
                top: scrollHeight,
                behavior: 'auto'
            });

            // 等待一下,然后再额外滚动
            setTimeout(() => {
                window.scrollBy({
                    top: config.extraScroll,
                    behavior: 'auto'
                });
                debugLog(`激进滚动:scrollTo(scrollHeight) + scrollBy(${config.extraScroll})`);
            }, 300);

        } else if (config.aggressiveness === 'extreme') {
            // 极限模式:多次超量滚动
            window.scrollTo({
                top: scrollHeight + config.extraScroll,
                behavior: 'auto'
            });

            setTimeout(() => {
                window.scrollBy({
                    top: config.extraScroll,
                    behavior: 'auto'
                });

                // 第三次滚动,确保绝对到底
                setTimeout(() => {
                    window.scrollTo({
                        top: document.documentElement.scrollHeight + 5000,
                        behavior: 'auto'
                    });
                    debugLog('极限滚动:三次超量滚动,scrollHeight + ' + (config.extraScroll + 5000));
                }, 200);
            }, 200);
        }

        updateStatus('⏳ 等待B站加载新内容...');

        // 第一次等待:让滚动完成
        setTimeout(() => {
            updateStatus('📡 等待服务器响应...');

            // 第二次等待:让内容渲染
            setTimeout(() => {
                checkDynamics();
            }, config.scrollDelay);
        }, 800);
    }

    function checkDynamics() {
        let dynamicCards = document.querySelectorAll('.bili-dyn-item');
        const currentCount = dynamicCards.length;
        const config = window.jumpConfig;

        debugLog(`检查动态 - 当前:${currentCount}条, 上次:${lastDynamicCount}条`);

        document.getElementById('loaded-count').textContent = currentCount;
        document.getElementById('retry-count').textContent = currentRetries;

        const hasNewContent = currentCount > lastDynamicCount;

        if (hasNewContent) {
            const newCount = currentCount - lastDynamicCount;
            debugLog(`✓ 成功加载 ${newCount} 条新动态`);
            currentRetries = 0;
            lastDynamicCount = currentCount;
            updateStatus(`✅ 新增 ${newCount} 条动态`);
            document.getElementById('retry-count').textContent = '0';
        } else {
            currentRetries++;
            debugLog(`✗ 未检测到新动态 (${currentRetries}/${config.maxRetries})`);
            updateStatus(`⏳ 未检测到新内容,重试中... (${currentRetries}/${config.maxRetries})`);

            if (currentRetries >= config.maxRetries) {
                handleReachedBottom();
                return;
            }
        }

        // 分析当前的动态
        if (currentCount > 0) {
            const result = analyzeDynamics(dynamicCards);

            if (result.found) {
                highlightTargetDynamic(result.targetCard);
                stopJump();
                const d = result.targetDate;
                const dateStr = targetDay
                    ? `${d.year}年${d.month}月${d.day}日`
                    : `${d.year}年${d.month}月`;
                alert(`🎉 找到目标动态!\n\n时间: ${dateStr}\n\n已为您高亮显示该动态。`);
                return;
            } else if (result.passed) {
                handlePassedTarget(result);
                return;
            } else if (result.latestDate) {
                const d = result.latestDate;
                document.getElementById('current-date').textContent =
                    `${d.year}年${d.month}月${d.day}日`;
            }
        }

        // 继续滚动
        setTimeout(autoScroll, 500);
    }

    // 关键修复:分析动态数组,查找目标
    function analyzeDynamics(dynamicCards) {
        const checkCount = Math.min(20, dynamicCards.length);
        let latestDate = null;
        let oldestDate = null;
        let targetCard = null;
        let targetDate = null;

        // 第一遍:从后往前完整遍历,寻找匹配项
        for (let i = dynamicCards.length - 1; i >= dynamicCards.length - checkCount && i >= 0; i--) {
            const card = dynamicCards[i];
            const dateInfo = extractDateInfo(card);

            if (!dateInfo) continue;

            // 记录最新(最靠后)的日期
            if (!latestDate || dateInfo.timestamp > latestDate.timestamp) {
                latestDate = dateInfo;
            }

            // 记录最旧(最靠前)的日期
            if (!oldestDate || dateInfo.timestamp < oldestDate.timestamp) {
                oldestDate = dateInfo;
            }

            // 检查是否匹配目标
            let isMatch = false;
            if (targetDay) {
                // 如果指定了具体日期,需要年月日都匹配
                isMatch = dateInfo.year === targetYear &&
                         dateInfo.month === targetMonth &&
                         dateInfo.day === targetDay;
                debugLog(`检查动态 #${i}: ${dateInfo.year}-${dateInfo.month}-${dateInfo.day}, 目标: ${targetYear}-${targetMonth}-${targetDay}, 匹配: ${isMatch}`);
            } else {
                // 如果没指定日期,只需要年月匹配
                isMatch = dateInfo.year === targetYear &&
                         dateInfo.month === targetMonth;
                debugLog(`检查动态 #${i}: ${dateInfo.year}-${dateInfo.month}, 目标: ${targetYear}-${targetMonth}, 匹配: ${isMatch}`);
            }

            // 如果找到匹配项,立即返回
            if (isMatch) {
                targetCard = card;
                targetDate = dateInfo;
                const dateStr = targetDay
                    ? `${dateInfo.year}年${dateInfo.month}月${dateInfo.day}日`
                    : `${dateInfo.year}年${dateInfo.month}月`;
                debugLog(`🎯 找到目标! ${dateStr}`);
                return { found: true, targetCard, targetDate, latestDate };
            }
        }

        // 第二步:如果没找到匹配项,检查是否已经超过目标时间
        // 判断依据:最旧的那条动态的时间是否早于目标时间
        if (oldestDate) {
            let hasPassed = false;
            if (targetDay) {
                // 指定了日期的情况:需要比较到日
                hasPassed = oldestDate.year < targetYear ||
                    (oldestDate.year === targetYear && oldestDate.month < targetMonth) ||
                    (oldestDate.year === targetYear && oldestDate.month === targetMonth && oldestDate.day < targetDay);
            } else {
                // 未指定日期的情况:只比较到月
                hasPassed = oldestDate.year < targetYear ||
                    (oldestDate.year === targetYear && oldestDate.month < targetMonth);
            }

            if (hasPassed) {
                debugLog(`⚠️ 已超过目标时间: 最旧动态为 ${oldestDate.year}年${oldestDate.month}月${oldestDate.day}日`);
                return { found: false, passed: true, passedDate: oldestDate, latestDate };
            }
        }

        // 既没找到,也没超过,继续滚动
        return { found: false, passed: false, latestDate };
    }

    function handleReachedBottom() {
        stopJump();

        const dynamicCards = document.querySelectorAll('.bili-dyn-item');
        if (dynamicCards.length > 0) {
            const lastCard = dynamicCards[dynamicCards.length - 1];
            const lastDate = extractDateInfo(lastCard);

            if (lastDate) {
                const targetDesc = targetDay
                    ? `${targetYear}年${targetMonth}月${targetDay}日`
                    : `${targetYear}年${targetMonth}月`;
                const msg = `📍 已达到搜索限制\n\n` +
                           `最早动态: ${lastDate.year}年${lastDate.month}月${lastDate.day}日\n` +
                           `目标时间: ${targetDesc}\n` +
                           `已加载动态: ${dynamicCards.length}条\n\n` +
                           `可能原因:\n` +
                           `• 该UP主在目标时间未发布动态\n` +
                           `• 早期动态已被删除\n` +
                           `• B站对历史动态有显示限制\n\n` +
                           `建议:\n` +
                           `• 尝试增加"最大重试次数"到20-30\n` +
                           `• 使用"极限"滚动模式\n` +
                           `• 增加"滚动等待时间"到3000-4000ms`;
                alert(msg);
            } else {
                alert('⚠️ 已达到搜索限制,但无法识别最后一条动态的时间。');
            }
        } else {
            alert('⚠️ 未能加载到动态内容。请刷新页面后重试。');
        }
    }

    function handlePassedTarget(result) {
        stopJump();
        const d = result.passedDate;
        const targetDesc = targetDay
            ? `${targetYear}年${targetMonth}月${targetDay}日`
            : `${targetYear}年${targetMonth}月`;
        alert(`⚠️ 已超过目标时间\n\n当前位置: ${d.year}年${d.month}月${d.day}日\n目标时间: ${targetDesc}\n\n在当前位置附近未找到目标时间的动态。\n\n建议: 可以尝试手动向上滚动查找,或者该UP主在目标时间可能没有发动态。`);
    }

    function extractDateInfo(dynamicCard) {
        try {
            const timeElement = dynamicCard.querySelector('.bili-dyn-time');
            if (timeElement) {
                const timeText = timeElement.textContent.trim();
                return parseTimeText(timeText);
            }

            const timeSelectors = ['[class*="time"]', 'span[class*="time"]'];
            for (const selector of timeSelectors) {
                const el = dynamicCard.querySelector(selector);
                if (el) {
                    const parsed = parseTimeText(el.textContent.trim());
                    if (parsed) return parsed;
                }
            }

            return null;
        } catch (error) {
            return null;
        }
    }

    function parseTimeText(timeText) {
        const now = new Date();

        try {
            let match = timeText.match(/(\d{4})[-年](\d{1,2})[-月](\d{1,2})/);
            if (match) {
                return {
                    year: parseInt(match[1]),
                    month: parseInt(match[2]),
                    day: parseInt(match[3]),
                    timestamp: new Date(match[1], match[2] - 1, match[3]).getTime() / 1000,
                    original: timeText
                };
            }

            match = timeText.match(/(\d{1,2})[-月](\d{1,2})/);
            if (match) {
                const month = parseInt(match[1]);
                const day = parseInt(match[2]);
                let year = now.getFullYear();
                if (month > now.getMonth() + 1) year--;
                return {
                    year: year,
                    month: month,
                    day: day,
                    timestamp: new Date(year, month - 1, day).getTime() / 1000,
                    original: timeText
                };
            }

            if (timeText.includes('分钟前')) {
                match = timeText.match(/(\d+)分钟前/);
                if (match) {
                    const date = new Date(now.getTime() - parseInt(match[1]) * 60000);
                    return {
                        year: date.getFullYear(),
                        month: date.getMonth() + 1,
                        day: date.getDate(),
                        timestamp: date.getTime() / 1000,
                        original: timeText
                    };
                }
            }

            if (timeText.includes('小时前')) {
                match = timeText.match(/(\d+)小时前/);
                if (match) {
                    const date = new Date(now.getTime() - parseInt(match[1]) * 3600000);
                    return {
                        year: date.getFullYear(),
                        month: date.getMonth() + 1,
                        day: date.getDate(),
                        timestamp: date.getTime() / 1000,
                        original: timeText
                    };
                }
            }

            if (timeText.includes('昨天')) {
                const date = new Date(now.getTime() - 86400000);
                return {
                    year: date.getFullYear(),
                    month: date.getMonth() + 1,
                    day: date.getDate(),
                    timestamp: date.getTime() / 1000,
                    original: timeText
                };
            }

            if (timeText.includes('前天')) {
                const date = new Date(now.getTime() - 172800000);
                return {
                    year: date.getFullYear(),
                    month: date.getMonth() + 1,
                    day: date.getDate(),
                    timestamp: date.getTime() / 1000,
                    original: timeText
                };
            }

            return null;
        } catch (error) {
            return null;
        }
    }

    function highlightTargetDynamic(dynamicCard) {
        dynamicCard.style.border = '4px solid #667eea';
        dynamicCard.style.boxShadow = '0 0 40px rgba(102, 126, 234, 0.8)';
        dynamicCard.style.transition = 'all 0.3s ease';
        dynamicCard.style.backgroundColor = '#f0f4ff';
        dynamicCard.style.position = 'relative';

        const badge = document.createElement('div');
        badge.innerHTML = '🎯 找到了!';
        badge.style.cssText = `
            position: absolute;
            top: -18px;
            left: 50%;
            transform: translateX(-50%);
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 8px 20px;
            border-radius: 25px;
            font-size: 15px;
            font-weight: bold;
            z-index: 100;
            box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
            animation: bounce 0.6s ease;
        `;

        // 添加动画样式
        const style = document.createElement('style');
        style.textContent = `
            @keyframes bounce {
                0%, 100% { transform: translateX(-50%) translateY(0); }
                50% { transform: translateX(-50%) translateY(-10px); }
            }
        `;
        document.head.appendChild(style);

        dynamicCard.insertBefore(badge, dynamicCard.firstChild);

        // 滚动到目标
        setTimeout(() => {
            dynamicCard.scrollIntoView({
                behavior: 'smooth',
                block: 'center'
            });
        }, 400);

        // 闪烁效果
        let blinks = 0;
        const blinkInterval = setInterval(() => {
            dynamicCard.style.backgroundColor =
                blinks % 2 === 0 ? '#fffacd' : '#f0f4ff';
            blinks++;
            if (blinks > 10) {
                clearInterval(blinkInterval);
                dynamicCard.style.backgroundColor = '#f0f4ff';
            }
        }, 350);

        debugLog('✓ 已高亮目标动态');
    }

    // 初始化
    function init() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                setTimeout(createControlPanel, 1500);
            });
        } else {
            setTimeout(createControlPanel, 1500);
        }
    }

    init();

    debugLog('========================================');
    debugLog('B站动态日期跳转助手 v1.0.3 已启动');
    debugLog('更新内容: 设置自动保存、默认最小化UI、逻辑优化');
    debugLog('========================================');
    console.log('%c[动态跳转] 脚本v1.0.3已加载! 设置将自动保存', 'color: #667eea; font-size: 14px; font-weight: bold; background: #f0f4ff; padding: 4px 8px; border-radius: 4px;');
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址